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

Example code for every feature. More...

Modules

 Sample helpers
 Helper utilities used in the sample.
 

Functions

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

Detailed Description

Example code for every feature.

This file does a quick tour of ryml.

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

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

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

Happy ryml'ing!

Note on use of raw strings

This sample uses multiline C strings instead of C++11 R"(raw strings)" because the doxygen parser is badly broken when handling raw strings.

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

Now run the following commands in the same folder:

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

Function Documentation

◆ sample_lightning_overview()

void sample_lightning_overview ( )

a lightning tour over most features see sample_quick_overview

Definition at line 340 of file quickstart.cpp.

341 {
342  // Parse YAML code in place, potentially mutating the buffer:
343  char yml_buf[] = "{foo: 1, bar: [2, 3], john: doe}";
344  ryml::Tree tree = ryml::parse_in_place(yml_buf);
345 
346  // read from the tree:
347  ryml::NodeRef bar = tree["bar"];
348  CHECK(bar[0].val() == "2");
349  CHECK(bar[1].val() == "3");
350  CHECK(bar[0].val().str == yml_buf + 15); // points at the source buffer
351  CHECK(bar[1].val().str == yml_buf + 18);
352 
353  // deserializing:
354  int bar0 = 0, bar1 = 0;
355  bar[0] >> bar0;
356  bar[1] >> bar1;
357  CHECK(bar0 == 2);
358  CHECK(bar1 == 3);
359 
360  // serializing:
361  bar[0] << 10; // creates a string in the tree's arena
362  bar[1] << 11;
363  CHECK(bar[0].val() == "10");
364  CHECK(bar[1].val() == "11");
365 
366  // add nodes
367  bar.append_child() << 12; // see also operator= (explanation below)
368  CHECK(bar[2].val() == "12");
369 
370  // emit tree
371  std::string expected = "{foo: 1,bar: [10,11,12],john: doe}";
372  // emit tree to std::string
373  CHECK(ryml::emitrs_yaml<std::string>(tree) == expected);
374  // emit tree to FILE*
375  ryml::emit_yaml(tree, stdout); printf("\n");
376  // emit tree to ostream
377  std::cout << tree << "\n";
378 
379  // emit node
380  ryml::ConstNodeRef foo = tree["foo"];
381  expected = "foo: 1\n";
382  // emit node to std::string
383  CHECK(ryml::emitrs_yaml<std::string>(foo) == expected);
384  // emit node to FILE*
385  ryml::emit_yaml(foo, stdout);
386  // emit node to ostream
387  std::cout << foo;
388 }
Holds a pointer to an existing tree, and a node id.
Definition: node.hpp:856
A reference to a node in an existing yaml tree, offering a more convenient API than the index-based A...
Definition: node.hpp:996
NodeRef append_child()
Definition: node.hpp:1414
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:416
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:38
#define CHECK(predicate)
a quick'n'dirty assertion to verify a predicate
Definition: quickstart.cpp:309

◆ sample_quick_overview()

void sample_quick_overview ( )

a brief tour over most features

Definition at line 394 of file quickstart.cpp.

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

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

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

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

1840 {
1841  const char filename[] = "ryml_example.yml";
1842  ryml::csubstr yaml = ""
1843  "foo: 1" "\n"
1844  "bar:" "\n"
1845  "- 2" "\n"
1846  "- 3" "\n";
1847  // because this is a minimal sample, it assumes nothing on the
1848  // environment/OS (other than that it can read/write files). So we
1849  // create the file on the fly:
1850  file_put_contents(filename, yaml);
1851 
1852  // now we can load it into a std::string (for example):
1853  {
1854  std::string contents = file_get_contents<std::string>(filename);
1855  ryml::Tree tree = ryml::parse_in_arena(ryml::to_csubstr(contents)); // immutable (csubstr) overload
1856  CHECK(tree["foo"].val() == "1");
1857  CHECK(tree["bar"][0].val() == "2");
1858  CHECK(tree["bar"][1].val() == "3");
1859  }
1860 
1861  // or we can use a vector<char> instead:
1862  {
1863  std::vector<char> contents = file_get_contents<std::vector<char>>(filename);
1864  ryml::Tree tree = ryml::parse_in_place(ryml::to_substr(contents)); // mutable (csubstr) overload
1865  CHECK(tree["foo"].val() == "1");
1866  CHECK(tree["bar"][0].val() == "2");
1867  CHECK(tree["bar"][1].val() == "3");
1868  }
1869 
1870  // generally, any contiguous char container can be used with ryml,
1871  // provided that the ryml::substr/ryml::csubstr view can be
1872  // created out of it.
1873  //
1874  // ryml provides the overloads above for these two containers, but
1875  // if you are using another container it should be very easy (only
1876  // requires pointer and length).
1877 }
void file_put_contents(const char *filename, CharContainer const &v, const char *access="wb")
save a buffer into a file

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

1885 {
1886  // Like the name suggests, parse_in_place() directly mutates the
1887  // source buffer in place
1888  char src[] = "{foo: 1, bar: [2, 3]}"; // ryml can parse in situ
1889  ryml::substr srcview = src; // a mutable view to the source buffer
1890  ryml::Tree tree = ryml::parse_in_place(srcview); // you can also reuse the tree and/or parser
1891  ryml::ConstNodeRef root = tree.crootref(); // get a constant reference to the root
1892 
1893  CHECK(root.is_map());
1894  CHECK(root["foo"].is_keyval());
1895  CHECK(root["foo"].key() == "foo");
1896  CHECK(root["foo"].val() == "1");
1897  CHECK(root["bar"].is_seq());
1898  CHECK(root["bar"].has_key());
1899  CHECK(root["bar"].key() == "bar");
1900  CHECK(root["bar"][0].val() == "2");
1901  CHECK(root["bar"][1].val() == "3");
1902 
1903  // deserializing:
1904  int foo = 0, bar0 = 0, bar1 = 0;
1905  root["foo"] >> foo;
1906  root["bar"][0] >> bar0;
1907  root["bar"][1] >> bar1;
1908  CHECK(foo == 1);
1909  CHECK(bar0 == 2);
1910  CHECK(bar1 == 3);
1911 
1912  // after parsing, the tree holds views to the source buffer:
1913  CHECK(root["foo"].val().data() == src + strlen("{foo: "));
1914  CHECK(root["foo"].val().begin() == src + strlen("{foo: "));
1915  CHECK(root["foo"].val().end() == src + strlen("{foo: 1"));
1916  CHECK(root["foo"].val().is_sub(srcview)); // equivalent to the previous three assertions
1917  CHECK(root["bar"][0].val().data() == src + strlen("{foo: 1, bar: ["));
1918  CHECK(root["bar"][0].val().begin() == src + strlen("{foo: 1, bar: ["));
1919  CHECK(root["bar"][0].val().end() == src + strlen("{foo: 1, bar: [2"));
1920  CHECK(root["bar"][0].val().is_sub(srcview)); // equivalent to the previous three assertions
1921  CHECK(root["bar"][1].val().data() == src + strlen("{foo: 1, bar: [2, "));
1922  CHECK(root["bar"][1].val().begin() == src + strlen("{foo: 1, bar: [2, "));
1923  CHECK(root["bar"][1].val().end() == src + strlen("{foo: 1, bar: [2, 3"));
1924  CHECK(root["bar"][1].val().is_sub(srcview)); // equivalent to the previous three assertions
1925 
1926  // NOTE. parse_in_place() cannot accept ryml::csubstr
1927  // so this will cause a /compile/ error:
1928  ryml::csubstr csrcview = srcview; // ok, can assign from mutable to immutable
1929  //tree = ryml::parse_in_place(csrcview); // compile error, cannot mutate an immutable view
1930  (void)csrcview;
1931 }
ConstNodeRef crootref() const
Get the root as a ConstNodeRef.
Definition: tree.cpp:65

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

1939 {
1940  // to parse read-only memory, ryml will copy first to the tree's
1941  // arena, and then parse the copied buffer:
1942  ryml::Tree tree = ryml::parse_in_arena("{foo: 1, bar: [2, 3]}");
1943  ryml::ConstNodeRef root = tree.crootref(); // get a const reference to the root
1944 
1945  CHECK(root.is_map());
1946  CHECK(root["foo"].is_keyval());
1947  CHECK(root["foo"].key() == "foo");
1948  CHECK(root["foo"].val() == "1");
1949  CHECK(root["bar"].is_seq());
1950  CHECK(root["bar"].has_key());
1951  CHECK(root["bar"].key() == "bar");
1952  CHECK(root["bar"][0].val() == "2");
1953  CHECK(root["bar"][1].val() == "3");
1954 
1955  // deserializing:
1956  int foo = 0, bar0 = 0, bar1 = 0;
1957  root["foo"] >> foo;
1958  root["bar"][0] >> bar0;
1959  root["bar"][1] >> bar1;
1960  CHECK(foo == 1);
1961  CHECK(bar0 == 2);
1962  CHECK(bar1 == 3);
1963 
1964  // NOTE. parse_in_arena() cannot accept ryml::substr. Overloads
1965  // receiving substr buffers are declared, but intentionally left
1966  // undefined, so this will cause a /linker/ error
1967  char src[] = "{foo: is it really true}";
1968  ryml::substr srcview = src;
1969  //tree = ryml::parse_in_place(srcview); // linker error, overload intentionally undefined
1970 
1971  // If you really intend to parse a mutable buffer in the arena,
1972  // then simply convert it to immutable prior to calling
1973  // parse_in_arena():
1974  ryml::csubstr csrcview = srcview; // assigning from src also works
1975  tree = ryml::parse_in_arena(csrcview); // OK! csrcview is immutable
1976  CHECK(tree["foo"].val() == "is it really true");
1977 }

◆ sample_parse_reuse_tree()

void sample_parse_reuse_tree ( )

demonstrate reuse/modification of tree when parsing

See also
Parse utilities

Definition at line 1984 of file quickstart.cpp.

1985 {
1986  ryml::Tree tree;
1987 
1988  // it will always be faster if the tree's size is conveniently reserved:
1989  tree.reserve(30); // reserve 30 nodes (good enough for this sample)
1990  // if you are using the tree's arena to serialize data,
1991  // then reserve also the arena's size:
1992  tree.reserve_arena(256); // reserve 256 characters (good enough for this sample)
1993 
1994  // now parse into the tree:
1995  ryml::csubstr yaml = ""
1996  "foo: 1" "\n"
1997  "bar: [2,3]" "\n"
1998  "";
1999  ryml::parse_in_arena(yaml, &tree);
2000 
2001  ryml::ConstNodeRef root = tree.crootref();
2002  CHECK(root.num_children() == 2);
2003  CHECK(root.is_map());
2004  CHECK(root["foo"].is_keyval());
2005  CHECK(root["foo"].key() == "foo");
2006  CHECK(root["foo"].val() == "1");
2007  CHECK(root["bar"].is_seq());
2008  CHECK(root["bar"].has_key());
2009  CHECK(root["bar"].key() == "bar");
2010  CHECK(root["bar"][0].val() == "2");
2011  CHECK(root["bar"][1].val() == "3");
2012  CHECK(ryml::emitrs_yaml<std::string>(tree) == yaml);
2013 
2014  // WATCHOUT: parsing into an existing tree will APPEND to it:
2015  ryml::parse_in_arena("{foo2: 12, bar2: [22, 32]}", &tree);
2016  CHECK(ryml::emitrs_yaml<std::string>(tree) == ""
2017  "foo: 1" "\n"
2018  "bar: [2,3]" "\n"
2019  "foo2: 12" "\n"
2020  "bar2: [22,32]" "\n"
2021  "");
2022  CHECK(root.num_children() == 4);
2023  CHECK(root["foo2"].is_keyval());
2024  CHECK(root["foo2"].key() == "foo2");
2025  CHECK(root["foo2"].val() == "12");
2026  CHECK(root["bar2"].is_seq());
2027  CHECK(root["bar2"].has_key());
2028  CHECK(root["bar2"].key() == "bar2");
2029  CHECK(root["bar2"][0].val() == "22");
2030  CHECK(root["bar2"][1].val() == "32");
2031 
2032  // if you want to fully replace the tree, you need to clear first
2033  // before parsing into an existing tree:
2034  tree.clear();
2035  tree.clear_arena(); // you may or may not want to clear the arena
2036  ryml::parse_in_arena("- a\n- b\n- {x0: 1, x1: 2}", &tree);
2037  CHECK(ryml::emitrs_yaml<std::string>(tree) == "- a\n- b\n- {x0: 1,x1: 2}\n");
2038  CHECK(root.is_seq());
2039  CHECK(root[0].val() == "a");
2040  CHECK(root[1].val() == "b");
2041  CHECK(root[2].is_map());
2042  CHECK(root[2]["x0"].val() == "1");
2043  CHECK(root[2]["x1"].val() == "2");
2044 
2045  // we can parse directly into a node nested deep in an existing tree:
2046  ryml::NodeRef mroot = tree.rootref(); // modifiable root
2047  ryml::parse_in_arena("champagne: Dom Perignon\ncoffee: Arabica", mroot.append_child());
2048  CHECK(ryml::emitrs_yaml<std::string>(tree) == ""
2049  "- a" "\n"
2050  "- b" "\n"
2051  "- {x0: 1,x1: 2}" "\n"
2052  "- champagne: Dom Perignon" "\n"
2053  " coffee: Arabica" "\n"
2054  "");
2055  CHECK(root.is_seq());
2056  CHECK(root[0].val() == "a");
2057  CHECK(root[1].val() == "b");
2058  CHECK(root[2].is_map());
2059  CHECK(root[2]["x0"].val() == "1");
2060  CHECK(root[2]["x1"].val() == "2");
2061  CHECK(root[3].is_map());
2062  CHECK(root[3]["champagne"].val() == "Dom Perignon");
2063  CHECK(root[3]["coffee"].val() == "Arabica");
2064 
2065  // watchout: to add to an existing node within a map, the node's
2066  // key must be separately set first:
2067  ryml::NodeRef more = mroot[3].append_child({ryml::KEYMAP, "more"});
2068  ryml::NodeRef beer = mroot[3].append_child({ryml::KEYSEQ, "beer"});
2069  ryml::NodeRef always = mroot[3].append_child({ryml::KEY, "always"});
2070  ryml::parse_in_arena("{vinho verde: Soalheiro, vinho tinto: Redoma 2017}", more);
2071  ryml::parse_in_arena("- Rochefort 10\n- Busch\n- Leffe Rituel", beer);
2072  ryml::parse_in_arena("lots\nof\nwater", always);
2073  CHECK(ryml::emitrs_yaml<std::string>(tree) == ""
2074  "- a" "\n"
2075  "- b" "\n"
2076  "- {x0: 1,x1: 2}" "\n"
2077  "- champagne: Dom Perignon" "\n"
2078  " coffee: Arabica" "\n"
2079  // note these were added:
2080  " more:" "\n"
2081  " vinho verde: Soalheiro" "\n"
2082  " vinho tinto: Redoma 2017" "\n"
2083  " beer:" "\n"
2084  " - Rochefort 10" "\n"
2085  " - Busch" "\n"
2086  " - Leffe Rituel" "\n"
2087  " always: lots of water" "\n"
2088  "");
2089 
2090  // can append at the top:
2091  ryml::parse_in_arena("- foo\n- bar\n- baz\n- bat", mroot);
2092  CHECK(ryml::emitrs_yaml<std::string>(tree) == ""
2093  "- a" "\n"
2094  "- b" "\n"
2095  "- {x0: 1,x1: 2}" "\n"
2096  "- champagne: Dom Perignon" "\n"
2097  " coffee: Arabica" "\n"
2098  " more:" "\n"
2099  " vinho verde: Soalheiro" "\n"
2100  " vinho tinto: Redoma 2017" "\n"
2101  " beer:" "\n"
2102  " - Rochefort 10" "\n"
2103  " - Busch" "\n"
2104  " - Leffe Rituel" "\n"
2105  " always: lots of water" "\n"
2106  // note these were added
2107  "- foo" "\n"
2108  "- bar" "\n"
2109  "- baz" "\n"
2110  "- bat" "\n"
2111  "");
2112 
2113  // or nested:
2114  ryml::parse_in_arena("[Kasteel Donker]", beer);
2115  CHECK(ryml::emitrs_yaml<std::string>(tree) ==
2116  "- a" "\n"
2117  "- b" "\n"
2118  "- {x0: 1,x1: 2}" "\n"
2119  "- champagne: Dom Perignon" "\n"
2120  " coffee: Arabica" "\n"
2121  " more:" "\n"
2122  " vinho verde: Soalheiro" "\n"
2123  " vinho tinto: Redoma 2017" "\n"
2124  " beer:" "\n"
2125  " - Rochefort 10" "\n"
2126  " - Busch" "\n"
2127  " - Leffe Rituel" "\n"
2128  " - Kasteel Donker" "\n" // added this
2129  " always: lots of water" "\n"
2130  "- foo" "\n"
2131  "- bar" "\n"
2132  "- baz" "\n"
2133  "- bat" "\n"
2134  "");
2135 }
void clear()
clear the tree and zero every node
Definition: tree.cpp:330
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:971
void reserve(id_type node_capacity=RYML_DEFAULT_TREE_CAPACITY)
Definition: tree.cpp:292
void clear_arena()
Definition: tree.hpp:280
@ KEY
is member of a map
Definition: node_type.hpp:37

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

2144 {
2145  ryml::EventHandlerTree evt_handler = {};
2146  ryml::Parser parser(&evt_handler);
2147 
2148  // it is also advised to reserve the parser depth
2149  // to the expected depth of the data tree:
2150  parser.reserve_stack(10); // uses small storage optimization
2151  // defaulting to 16 depth, so this
2152  // instruction is a no-op, and the stack
2153  // will located in the parser object.
2154  parser.reserve_stack(20); // But this will cause an allocation
2155  // because it is above 16.
2156 
2157  ryml::csubstr yaml = "[Dom Perignon,Gosset Grande Reserve,Jacquesson 742]";
2158  ryml::Tree champagnes = parse_in_arena(&parser, "champagnes.yml", yaml);
2159  CHECK(ryml::emitrs_yaml<std::string>(champagnes) == yaml);
2160 
2161  yaml = "[Rochefort 10,Busch,Leffe Rituel,Kasteel Donker]";
2162  ryml::Tree beers = parse_in_arena(&parser, "beers.yml", yaml);
2163  CHECK(ryml::emitrs_yaml<std::string>(beers) == yaml);
2164 }

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

2173 {
2174  ryml::Tree tree;
2175  // it will always be faster if the tree's size is conveniently reserved:
2176  tree.reserve(30); // reserve 30 nodes (good enough for this sample)
2177  // if you are using the tree's arena to serialize data,
2178  // then reserve also the arena's size:
2179  tree.reserve(256); // reserve 256 characters (good enough for this sample)
2180 
2181  ryml::EventHandlerTree evt_handler;
2182  ryml::Parser parser(&evt_handler);
2183  // it is also advised to reserve the parser depth
2184  // to the expected depth of the data tree:
2185  parser.reserve_stack(10); // the parser uses small storage
2186  // optimization defaulting to 16 depth,
2187  // so this instruction is a no-op, and
2188  // the stack will be located in the
2189  // parser object.
2190  parser.reserve_stack(20); // But this will cause an allocation
2191  // because it is above 16.
2192 
2193  ryml::csubstr champagnes = ""
2194  "- Dom Perignon\n"
2195  "- Gosset Grande Reserve\n"
2196  "- Jacquesson 742\n"
2197  "";
2198  ryml::csubstr beers = ""
2199  "- Rochefort 10\n"
2200  "- Busch\n"
2201  "- Leffe Rituel\n"
2202  "- Kasteel Donker\n"
2203  "";
2204  ryml::csubstr wines = ""
2205  "- Soalheiro\n"
2206  "- Niepoort Redoma 2017\n"
2207  "- Vina Esmeralda\n"
2208  "";
2209 
2210  parse_in_arena(&parser, "champagnes.yml", champagnes, &tree);
2211  CHECK(ryml::emitrs_yaml<std::string>(tree) == champagnes);
2212 
2213  // watchout: this will APPEND to the given tree:
2214  parse_in_arena(&parser, "beers.yml", beers, &tree);
2215  CHECK(ryml::emitrs_yaml<std::string>(tree) == ""
2216  "- Dom Perignon\n"
2217  "- Gosset Grande Reserve\n"
2218  "- Jacquesson 742\n"
2219  "- Rochefort 10\n"
2220  "- Busch\n"
2221  "- Leffe Rituel\n"
2222  "- Kasteel Donker\n"
2223  "");
2224 
2225  // if you don't wish to append, clear the tree first:
2226  tree.clear();
2227  parse_in_arena(&parser, "wines.yml", wines, &tree);
2228  CHECK(ryml::emitrs_yaml<std::string>(tree) == ""
2229  "- Soalheiro\n"
2230  "- Niepoort Redoma 2017\n"
2231  "- Vina Esmeralda\n"
2232  "");
2233 }

◆ sample_iterate_trees()

void sample_iterate_trees ( )

shows how to programatically iterate through trees

See also
Tree utilities
Node classes

Definition at line 2242 of file quickstart.cpp.

2243 {
2244  const ryml::Tree tree = ryml::parse_in_arena(
2245  "doe: a deer, a female deer" "\n"
2246  "ray: a drop of golden sun" "\n"
2247  "pi: 3.14159" "\n"
2248  "xmas: true" "\n"
2249  "french-hens: 3" "\n"
2250  "calling-birds:" "\n"
2251  " - huey" "\n"
2252  " - dewey" "\n"
2253  " - louie" "\n"
2254  " - fred" "\n"
2255  "xmas-fifth-day:" "\n"
2256  " calling-birds: four" "\n"
2257  " french-hens: 3" "\n"
2258  " golden-rings: 5" "\n"
2259  " partridges:" "\n"
2260  " count: 1" "\n"
2261  " location: a pear tree" "\n"
2262  " turtle-doves: two" "\n"
2263  "cars: GTO" "\n"
2264  "");
2265  ryml::ConstNodeRef root = tree.crootref();
2266 
2267  // iterate children
2268  {
2269  std::vector<ryml::csubstr> keys, vals; // to store all the root-level keys, vals
2270  for(ryml::ConstNodeRef n : root.children())
2271  {
2272  keys.emplace_back(n.key());
2273  vals.emplace_back(n.has_val() ? n.val() : ryml::csubstr{});
2274  }
2275  CHECK(keys.size() >= 6);
2276  CHECK(vals.size() >= 6);
2277  if(keys.size() >= 6 && vals.size() >= 6)
2278  {
2279  CHECK(keys[0] == "doe");
2280  CHECK(vals[0] == "a deer, a female deer");
2281  CHECK(keys[1] == "ray");
2282  CHECK(vals[1] == "a drop of golden sun");
2283  CHECK(keys[2] == "pi");
2284  CHECK(vals[2] == "3.14159");
2285  CHECK(keys[3] == "xmas");
2286  CHECK(vals[3] == "true");
2287  CHECK(root[5].has_key());
2288  CHECK(root[5].is_seq());
2289  CHECK(root[5].key() == "calling-birds");
2290  CHECK(!root[5].has_val()); // it is a map, so not a val
2291  //CHECK(root[5].val() == ""); // ERROR! node does not have a val.
2292  CHECK(keys[5] == "calling-birds");
2293  CHECK(vals[5] == "");
2294  }
2295  }
2296 
2297  // iterate siblings
2298  {
2299  size_t count = 0;
2300  ryml::csubstr calling_birds[] = {"huey", "dewey", "louie", "fred"};
2301  for(ryml::ConstNodeRef n : root["calling-birds"][2].siblings())
2302  CHECK(n.val() == calling_birds[count++]);
2303  CHECK(count == 4u);
2304  }
2305 }

◆ sample_create_trees()

void sample_create_trees ( )

shows how to programatically create trees

See also
Tree utilities
Node classes

Definition at line 2314 of file quickstart.cpp.

2315 {
2316  ryml::NodeRef doe;
2317  CHECK(doe.invalid()); // it's pointing at nowhere
2318 
2319  ryml::Tree tree;
2320  ryml::NodeRef root = tree.rootref();
2321  root |= ryml::MAP; // mark root as a map
2322  doe = root["doe"];
2323  CHECK(!doe.invalid()); // it's now pointing at the tree
2324  CHECK(doe.is_seed()); // but the tree has nothing there, so this is only a seed
2325 
2326  // set the value of the node
2327  const char a_deer[] = "a deer, a female deer";
2328  doe = a_deer;
2329  // now the node really exists in the tree, and this ref is no
2330  // longer a seed:
2331  CHECK(!doe.is_seed());
2332  // WATCHOUT for lifetimes:
2333  CHECK(doe.val().str == a_deer); // it is pointing at the initial string
2334  // If you need to avoid lifetime dependency, serialize the data:
2335  {
2336  std::string a_drop = "a drop of golden sun";
2337  // this will copy the string to the tree's arena:
2338  // (see the serialization samples below)
2339  root["ray"] << a_drop;
2340  // and now you can modify the original string without changing
2341  // the tree:
2342  a_drop[0] = 'Z';
2343  a_drop[1] = 'Z';
2344  }
2345  CHECK(root["ray"].val() == "a drop of golden sun");
2346 
2347  // etc.
2348  root["pi"] << ryml::fmt::real(3.141592654, 5);
2349  root["xmas"] << ryml::fmt::boolalpha(true);
2350  root["french-hens"] << 3;
2351  ryml::NodeRef calling_birds = root["calling-birds"];
2352  calling_birds |= ryml::SEQ;
2353  calling_birds.append_child() = "huey";
2354  calling_birds.append_child() = "dewey";
2355  calling_birds.append_child() = "louie";
2356  calling_birds.append_child() = "fred";
2357  ryml::NodeRef xmas5 = root["xmas-fifth-day"];
2358  xmas5 |= ryml::MAP;
2359  xmas5["calling-birds"] = "four";
2360  xmas5["french-hens"] << 3;
2361  xmas5["golden-rings"] << 5;
2362  xmas5["partridges"] |= ryml::MAP;
2363  xmas5["partridges"]["count"] << 1;
2364  xmas5["partridges"]["location"] = "a pear tree";
2365  xmas5["turtle-doves"] = "two";
2366  root["cars"] = "GTO";
2367 
2368  CHECK(ryml::emitrs_yaml<std::string>(tree) == ""
2369  "doe: a deer, a female deer" "\n"
2370  "ray: a drop of golden sun" "\n"
2371  "pi: 3.14159" "\n"
2372  "xmas: true" "\n"
2373  "french-hens: 3" "\n"
2374  "calling-birds:" "\n"
2375  " - huey" "\n"
2376  " - dewey" "\n"
2377  " - louie" "\n"
2378  " - fred" "\n"
2379  "xmas-fifth-day:" "\n"
2380  " calling-birds: four" "\n"
2381  " french-hens: 3" "\n"
2382  " golden-rings: 5" "\n"
2383  " partridges:" "\n"
2384  " count: 1" "\n"
2385  " location: a pear tree" "\n"
2386  " turtle-doves: two" "\n"
2387  "cars: GTO" "\n"
2388  "");
2389 }
boolalpha_ boolalpha(T const &val=false)
tag function to mark a variable to be written as an alphabetic boolean, ie as either true or false
Definition: format.hpp:70

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

2397 {
2398  // mutable buffers are parsed in situ:
2399  {
2400  char buf[] = "[a, b, c, d]";
2401  ryml::substr yml = buf;
2402  ryml::Tree tree = ryml::parse_in_place(yml);
2403  // notice the arena is empty:
2404  CHECK(tree.arena().empty());
2405  // and the tree is pointing at the original buffer:
2406  ryml::NodeRef root = tree.rootref();
2407  CHECK(root[0].val().is_sub(yml));
2408  CHECK(root[1].val().is_sub(yml));
2409  CHECK(root[2].val().is_sub(yml));
2410  CHECK(root[3].val().is_sub(yml));
2411  CHECK(yml.is_super(root[0].val()));
2412  CHECK(yml.is_super(root[1].val()));
2413  CHECK(yml.is_super(root[2].val()));
2414  CHECK(yml.is_super(root[3].val()));
2415  }
2416 
2417  // when parsing immutable buffers, the buffer is first copied to the
2418  // tree's arena; the copy in the arena is then the buffer which is
2419  // actually parsed
2420  {
2421  ryml::csubstr yml = "[a, b, c, d]";
2422  ryml::Tree tree = ryml::parse_in_arena(yml);
2423  // notice the buffer was copied to the arena:
2424  CHECK(tree.arena().data() != yml.data());
2425  CHECK(tree.arena() == yml);
2426  // and the tree is pointing at the arena instead of to the
2427  // original buffer:
2428  ryml::NodeRef root = tree.rootref();
2429  ryml::csubstr arena = tree.arena();
2430  CHECK(root[0].val().is_sub(arena));
2431  CHECK(root[1].val().is_sub(arena));
2432  CHECK(root[2].val().is_sub(arena));
2433  CHECK(root[3].val().is_sub(arena));
2434  CHECK(arena.is_super(root[0].val()));
2435  CHECK(arena.is_super(root[1].val()));
2436  CHECK(arena.is_super(root[2].val()));
2437  CHECK(arena.is_super(root[3].val()));
2438  }
2439 
2440  // the arena is also used when the data is serialized to string
2441  // with NodeRef::operator<<(): mutable buffer
2442  {
2443  char buf[] = "[a, b, c, d]"; // mutable
2444  ryml::substr yml = buf;
2445  ryml::Tree tree = ryml::parse_in_place(yml);
2446  // notice the arena is empty:
2447  CHECK(tree.arena().empty());
2448  ryml::NodeRef root = tree.rootref();
2449 
2450  // serialize an integer, and mutate the tree
2451  CHECK(root[2].val() == "c");
2452  CHECK(root[2].val().is_sub(yml)); // val is first pointing at the buffer
2453  root[2] << 12345;
2454  CHECK(root[2].val() == "12345");
2455  CHECK(root[2].val().is_sub(tree.arena())); // now val is pointing at the arena
2456  // notice the serialized string was appended to the tree's arena:
2457  CHECK(tree.arena() == "12345");
2458 
2459  // serialize an integer, and mutate the tree
2460  CHECK(root[3].val() == "d");
2461  CHECK(root[3].val().is_sub(yml)); // val is first pointing at the buffer
2462  root[3] << 67890;
2463  CHECK(root[3].val() == "67890");
2464  CHECK(root[3].val().is_sub(tree.arena())); // now val is pointing at the arena
2465  // notice the serialized string was appended to the tree's arena:
2466  CHECK(tree.arena() == "1234567890");
2467  }
2468  // the arena is also used when the data is serialized to string
2469  // with NodeRef::operator<<(): immutable buffer
2470  {
2471  ryml::csubstr yml = "[a, b, c, d]"; // immutable
2472  ryml::Tree tree = ryml::parse_in_arena(yml);
2473  // notice the buffer was copied to the arena:
2474  CHECK(tree.arena().data() != yml.data());
2475  CHECK(tree.arena() == yml);
2476  ryml::NodeRef root = tree.rootref();
2477 
2478  // serialize an integer, and mutate the tree
2479  CHECK(root[2].val() == "c");
2480  root[2] << 12345; // serialize an integer
2481  CHECK(root[2].val() == "12345");
2482  // notice the serialized string was appended to the tree's arena:
2483  // notice also the previous values remain there.
2484  // RYML DOES NOT KEEP TRACK OF REFERENCES TO THE ARENA.
2485  CHECK(tree.arena() == "[a, b, c, d]12345");
2486  // old values: --------------^
2487 
2488  // serialize an integer, and mutate the tree
2489  root[3] << 67890;
2490  CHECK(root[3].val() == "67890");
2491  // notice the serialized string was appended to the tree's arena:
2492  // notice also the previous values remain there.
2493  // RYML DOES NOT KEEP TRACK OF REFERENCES TO THE ARENA.
2494  CHECK(tree.arena() == "[a, b, c, d]1234567890");
2495  // old values: --------------^ ---^^^^^
2496  }
2497 
2498  // to_arena(): directly serialize values to the arena:
2499  {
2500  ryml::Tree tree = ryml::parse_in_arena("{a: b}");
2501  ryml::csubstr c10 = tree.to_arena(10101010);
2502  CHECK(c10 == "10101010");
2503  CHECK(c10.is_sub(tree.arena()));
2504  CHECK(tree.arena() == "{a: b}10101010");
2505  CHECK(tree.key(1) == "a");
2506  CHECK(tree.val(1) == "b");
2507  tree.set_val(1, c10);
2508  CHECK(tree.val(1) == c10);
2509  // and you can also do it through a node:
2510  ryml::NodeRef root = tree.rootref();
2511  root["a"].set_val_serialized(2222);
2512  CHECK(root["a"].val() == "2222");
2513  CHECK(tree.arena() == "{a: b}101010102222");
2514  }
2515 
2516  // copy_to_arena(): manually copy a string to the arena:
2517  {
2518  ryml::Tree tree = ryml::parse_in_arena("{a: b}");
2519  ryml::csubstr mystr = "Gosset Grande Reserve";
2520  ryml::csubstr copied = tree.copy_to_arena(mystr);
2521  CHECK(!copied.overlaps(mystr));
2522  CHECK(copied == mystr);
2523  CHECK(tree.arena() == "{a: b}Gosset Grande Reserve");
2524  }
2525 
2526  // alloc_arena(): allocate a buffer from the arena:
2527  {
2528  ryml::Tree tree = ryml::parse_in_arena("{a: b}");
2529  ryml::csubstr mystr = "Gosset Grande Reserve";
2530  ryml::substr copied = tree.alloc_arena(mystr.size());
2531  CHECK(!copied.overlaps(mystr));
2532  memcpy(copied.str, mystr.str, mystr.len);
2533  CHECK(copied == mystr);
2534  CHECK(tree.arena() == "{a: b}Gosset Grande Reserve");
2535  }
2536 
2537  // reserve_arena(): ensure the arena has a certain size to avoid reallocations
2538  {
2539  ryml::Tree tree = ryml::parse_in_arena("{a: b}");
2540  CHECK(tree.arena().size() == strlen("{a: b}"));
2541  tree.reserve_arena(100);
2542  CHECK(tree.arena_capacity() >= 100);
2543  CHECK(tree.arena().size() == strlen("{a: b}"));
2544  tree.to_arena(123456);
2545  CHECK(tree.arena().first(12) == "{a: b}123456");
2546  }
2547 }
size_t set_val_serialized(T const &v)
Definition: node.hpp:1263
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:959
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:909
size_t arena_capacity() const
get the current capacity of the tree's internal arena
Definition: tree.hpp:876
void set_val(id_type node, csubstr val)
Definition: tree.hpp:605
csubstr const & val(id_type node) const
Definition: tree.hpp:391
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:930

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

2562 {
2563  ryml::Tree tree;
2564  CHECK(tree.arena().empty());
2565  CHECK(tree.to_arena('a') == "a"); CHECK(tree.arena() == "a");
2566  CHECK(tree.to_arena("bcde") == "bcde"); CHECK(tree.arena() == "abcde");
2567  CHECK(tree.to_arena(unsigned(0)) == "0"); CHECK(tree.arena() == "abcde0");
2568  CHECK(tree.to_arena(int(1)) == "1"); CHECK(tree.arena() == "abcde01");
2569  CHECK(tree.to_arena(uint8_t(0)) == "0"); CHECK(tree.arena() == "abcde010");
2570  CHECK(tree.to_arena(uint16_t(1)) == "1"); CHECK(tree.arena() == "abcde0101");
2571  CHECK(tree.to_arena(uint32_t(2)) == "2"); CHECK(tree.arena() == "abcde01012");
2572  CHECK(tree.to_arena(uint64_t(3)) == "3"); CHECK(tree.arena() == "abcde010123");
2573  CHECK(tree.to_arena(int8_t( 4)) == "4"); CHECK(tree.arena() == "abcde0101234");
2574  CHECK(tree.to_arena(int8_t(-4)) == "-4"); CHECK(tree.arena() == "abcde0101234-4");
2575  CHECK(tree.to_arena(int16_t( 5)) == "5"); CHECK(tree.arena() == "abcde0101234-45");
2576  CHECK(tree.to_arena(int16_t(-5)) == "-5"); CHECK(tree.arena() == "abcde0101234-45-5");
2577  CHECK(tree.to_arena(int32_t( 6)) == "6"); CHECK(tree.arena() == "abcde0101234-45-56");
2578  CHECK(tree.to_arena(int32_t(-6)) == "-6"); CHECK(tree.arena() == "abcde0101234-45-56-6");
2579  CHECK(tree.to_arena(int64_t( 7)) == "7"); CHECK(tree.arena() == "abcde0101234-45-56-67");
2580  CHECK(tree.to_arena(int64_t(-7)) == "-7"); CHECK(tree.arena() == "abcde0101234-45-56-67-7");
2581  CHECK(tree.to_arena((void*)1) == "0x1"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x1");
2582  CHECK(tree.to_arena(float(0.124)) == "0.124"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.124");
2583  CHECK(tree.to_arena(double(0.234)) == "0.234"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.234");
2584 
2585  // write boolean values - see also sample_formatting()
2586  CHECK(tree.to_arena(bool(true)) == "1"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.2341");
2587  CHECK(tree.to_arena(bool(false)) == "0"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410");
2588  CHECK(tree.to_arena(c4::fmt::boolalpha(true)) == "true"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410true");
2589  CHECK(tree.to_arena(c4::fmt::boolalpha(false)) == "false"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410truefalse");
2590 
2591  // write special float values
2592  // see also sample_float_precision()
2593  const float fnan = std::numeric_limits<float >::quiet_NaN();
2594  const double dnan = std::numeric_limits<double>::quiet_NaN();
2595  const float finf = std::numeric_limits<float >::infinity();
2596  const double dinf = std::numeric_limits<double>::infinity();
2597  CHECK(tree.to_arena( finf) == ".inf"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410truefalse.inf");
2598  CHECK(tree.to_arena( dinf) == ".inf"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410truefalse.inf.inf");
2599  CHECK(tree.to_arena(-finf) == "-.inf"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410truefalse.inf.inf-.inf");
2600  CHECK(tree.to_arena(-dinf) == "-.inf"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410truefalse.inf.inf-.inf-.inf");
2601  CHECK(tree.to_arena( fnan) == ".nan"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410truefalse.inf.inf-.inf-.inf.nan");
2602  CHECK(tree.to_arena( dnan) == ".nan"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410truefalse.inf.inf-.inf-.inf.nan.nan");
2603 
2604  // read special float values
2605  // see also sample_float_precision()
2606  C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH("-Wfloat-equal");
2607  tree = ryml::parse_in_arena(R"({ninf: -.inf, pinf: .inf, nan: .nan})");
2608  float f = 0.f;
2609  double d = 0.;
2610  CHECK(f == 0.f);
2611  CHECK(d == 0.);
2612  tree["ninf"] >> f; CHECK(f == -finf);
2613  tree["ninf"] >> d; CHECK(d == -dinf);
2614  tree["pinf"] >> f; CHECK(f == finf);
2615  tree["pinf"] >> d; CHECK(d == dinf);
2616  tree["nan" ] >> f; CHECK(std::isnan(f));
2617  tree["nan" ] >> d; CHECK(std::isnan(d));
2618  C4_SUPPRESS_WARNING_GCC_CLANG_POP
2619 
2620  // value overflow detection:
2621  // (for integral types only)
2622  {
2623  // we will be detecting errors below, so we use this sample helper
2624  ScopedErrorHandlerExample err = {};
2625  ryml::Tree t(err.callbacks()); // instantiate with the error-detecting callbacks
2626  // create a simple tree with an int value
2627  ryml::parse_in_arena(R"({val: 258})", &t);
2628  // by default, overflow is not detected:
2629  uint8_t valu8 = 0;
2630  int8_t vali8 = 0;
2631  t["val"] >> valu8; CHECK(valu8 == 2); // not 257; it wrapped around
2632  t["val"] >> vali8; CHECK(vali8 == 2); // not 257; it wrapped around
2633  // ...but there are facilities to detect overflow
2634  CHECK(ryml::overflows<uint8_t>(t["val"].val()));
2635  CHECK(ryml::overflows<int8_t>(t["val"].val()));
2636  CHECK( ! ryml::overflows<int16_t>(t["val"].val()));
2637  // and there is a format helper
2638  CHECK(err.check_error_occurs([&]{
2639  auto checku8 = ryml::fmt::overflow_checked(valu8); // need to declare the wrapper type before using it with >>
2640  t["val"] >> checku8; // this will cause an error
2641  }));
2642  CHECK(err.check_error_occurs([&]{
2643  auto checki8 = ryml::fmt::overflow_checked(vali8); // need to declare the wrapper type before using it with >>
2644  t["val"] >> checki8; // this will cause an error
2645  }));
2646  }
2647 }

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

2655 {
2656  // reading empty/null values - see also sample_formatting()
2658  "plain:" "\n"
2659  "squoted: ''" "\n"
2660  "dquoted: \"\"" "\n"
2661  "literal: |" "\n"
2662  "folded: >" "\n"
2663  "all_null: [~, null, Null, NULL]" "\n"
2664  "non_null: [nULL, non_null, non null, null it is not]" "\n"
2665  "");
2666  // first, remember that .has_val() is a structural predicate
2667  // indicating the node is a leaf, and not a container.
2668  CHECK(tree["plain"].has_val()); // has a val, even if it's empty!
2669  CHECK(tree["squoted"].has_val());
2670  CHECK(tree["dquoted"].has_val());
2671  CHECK(tree["literal"].has_val());
2672  CHECK(tree["folded"].has_val());
2673  CHECK( ! tree["all_null"].has_val());
2674  CHECK( ! tree["non_null"].has_val());
2675  // In essence, has_val() is the logical opposite of is_container()
2676  CHECK( ! tree["plain"].is_container());
2677  CHECK( ! tree["squoted"].is_container());
2678  CHECK( ! tree["dquoted"].is_container());
2679  CHECK( ! tree["literal"].is_container());
2680  CHECK( ! tree["folded"].is_container());
2681  CHECK(tree["all_null"].is_container());
2682  CHECK(tree["non_null"].is_container());
2683  //
2684  // Right. How about the contents of each val?
2685  //
2686  // all of these scalars have zero-length:
2687  CHECK(tree["plain"].val().len == 0);
2688  CHECK(tree["squoted"].val().len == 0);
2689  CHECK(tree["dquoted"].val().len == 0);
2690  CHECK(tree["literal"].val().len == 0);
2691  CHECK(tree["folded"].val().len == 0);
2692  // but only the empty scalar has null string:
2693  CHECK(tree["plain"].val().str == nullptr);
2694  CHECK(tree["squoted"].val().str != nullptr);
2695  CHECK(tree["dquoted"].val().str != nullptr);
2696  CHECK(tree["literal"].val().str != nullptr);
2697  CHECK(tree["folded"].val().str != nullptr);
2698  // likewise, scalar comparison to nullptr has the same results:
2699  // (remember that .val() gives you the scalar value, node must
2700  // have a val, ie must be a leaf node, not a container)
2701  CHECK(tree["plain"].val() == nullptr);
2702  CHECK(tree["squoted"].val() != nullptr);
2703  CHECK(tree["dquoted"].val() != nullptr);
2704  CHECK(tree["literal"].val() != nullptr);
2705  CHECK(tree["folded"].val() != nullptr);
2706  // the tree and node classes provide the corresponding predicate
2707  // functions .key_is_null() and .val_is_null().
2708  // (note that these functions have the same preconditions as .val(),
2709  // because they need get the val to look into its contents)
2710  CHECK(tree["plain"].val_is_null());
2711  CHECK( ! tree["squoted"].val_is_null());
2712  CHECK( ! tree["dquoted"].val_is_null());
2713  CHECK( ! tree["literal"].val_is_null());
2714  CHECK( ! tree["folded"].val_is_null());
2715  // matching to null is case-sensitive. only the cases shown here
2716  // match to null:
2717  for(ryml::ConstNodeRef child : tree["all_null"].children())
2718  {
2719  CHECK(child.val() != nullptr); // it is pointing at a string, so it is not nullptr!
2720  CHECK(child.val_is_null());
2721  }
2722  for(ryml::ConstNodeRef child : tree["non_null"].children())
2723  {
2724  CHECK(child.val() != nullptr);
2725  CHECK( ! child.val_is_null());
2726  }
2727  //
2728  //
2729  // Because the meaning of null/~/empty will vary from application
2730  // to application, ryml makes no assumption on what should be
2731  // serialized as null. It leaves this decision to the user. But
2732  // it also provides the proper toolbox for the user to implement
2733  // its intended solution.
2734  //
2735  // writing/disambiguating null values:
2736  ryml::csubstr null = {};
2737  ryml::csubstr nonnull = "";
2738  ryml::csubstr strnull = "null";
2739  ryml::csubstr tilde = "~";
2740  CHECK(null .len == 0); CHECK(null .str == nullptr); CHECK(null == nullptr);
2741  CHECK(nonnull.len == 0); CHECK(nonnull.str != nullptr); CHECK(nonnull != nullptr);
2742  CHECK(strnull.len != 0); CHECK(strnull.str != nullptr); CHECK(strnull != nullptr);
2743  CHECK(tilde .len != 0); CHECK(tilde .str != nullptr); CHECK(tilde != nullptr);
2744  tree.clear();
2745  tree.clear_arena();
2746  tree.rootref() |= ryml::MAP;
2747  // serializes as an empty plain scalar:
2748  tree["empty_null"] << null; CHECK(tree.arena() == "");
2749  // serializes as an empty quoted scalar:
2750  tree["empty_nonnull"] << nonnull; CHECK(tree.arena() == "");
2751  // serializes as the normal 'null' string:
2752  tree["str_null"] << strnull; CHECK(tree.arena() == "null");
2753  // serializes as the normal '~' string:
2754  tree["str_tilde"] << tilde; CHECK(tree.arena() == "null~");
2755  // this is the resulting yaml:
2756  CHECK(ryml::emitrs_yaml<std::string>(tree) == ""
2757  "empty_null: " "\n"
2758  "empty_nonnull: ''" "\n"
2759  "str_null: null" "\n"
2760  "str_tilde: ~" "\n"
2761  "");
2762  // To enforce a particular concept of what is a null string, you
2763  // can use the appropriate condition based on pointer nulity or
2764  // other appropriate criteria.
2765  //
2766  // As an example, proper comparison to nullptr:
2767  auto null_if_nullptr = [](ryml::csubstr s) {
2768  return s.str == nullptr ? "null" : s;
2769  };
2770  tree["empty_null"] << null_if_nullptr(null);
2771  tree["empty_nonnull"] << null_if_nullptr(nonnull);
2772  tree["str_null"] << null_if_nullptr(strnull);
2773  tree["str_tilde"] << null_if_nullptr(tilde);
2774  // this is the resulting yaml:
2775  CHECK(ryml::emitrs_yaml<std::string>(tree) == ""
2776  "empty_null: null" "\n"
2777  "empty_nonnull: ''" "\n"
2778  "str_null: null" "\n"
2779  "str_tilde: ~" "\n"
2780  "");
2781  //
2782  // As another example, nulity check based on the YAML nulity
2783  // predicate:
2784  auto null_if_predicate = [](ryml::csubstr s) {
2785  return ryml::scalar_is_null(s) ? "null" : s;
2786  };
2787  tree["empty_null"] << null_if_predicate(null);
2788  tree["empty_nonnull"] << null_if_predicate(nonnull);
2789  tree["str_null"] << null_if_predicate(strnull);
2790  tree["str_tilde"] << null_if_predicate(tilde);
2791  // this is the resulting yaml:
2792  CHECK(ryml::emitrs_yaml<std::string>(tree) == ""
2793  "empty_null: null" "\n"
2794  "empty_nonnull: ''" "\n"
2795  "str_null: null" "\n"
2796  "str_tilde: null" "\n"
2797  "");
2798  //
2799  // As another example, nulity check based on the YAML nulity
2800  // predicate, but returning "~" to simbolize nulity:
2801  auto tilde_if_predicate = [](ryml::csubstr s) {
2802  return ryml::scalar_is_null(s) ? "~" : s;
2803  };
2804  tree["empty_null"] << tilde_if_predicate(null);
2805  tree["empty_nonnull"] << tilde_if_predicate(nonnull);
2806  tree["str_null"] << tilde_if_predicate(strnull);
2807  tree["str_tilde"] << tilde_if_predicate(tilde);
2808  // this is the resulting yaml:
2809  CHECK(ryml::emitrs_yaml<std::string>(tree) == ""
2810  "empty_null: ~" "\n"
2811  "empty_nonnull: ''" "\n"
2812  "str_null: ~" "\n"
2813  "str_tilde: ~" "\n"
2814  "");
2815 }
bool scalar_is_null(csubstr s) noexcept
YAML-sense query of nullity.
Definition: node_type.cpp:289

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

2826 {
2827  // format(), format_sub(), formatrs(): format arguments
2828  {
2829  char buf_[256] = {};
2830  ryml::substr buf = buf_;
2831  size_t size = ryml::format(buf, "a={} foo {} {} bar {}", 0.1, 10, 11, 12);
2832  CHECK(size == strlen("a=0.1 foo 10 11 bar 12"));
2833  CHECK(buf.first(size) == "a=0.1 foo 10 11 bar 12");
2834  // it is safe to call on an empty buffer:
2835  // returns the size needed for the result, and no overflow occurs:
2836  size = ryml::format({} , "a={} foo {} {} bar {}", "this_is_a", 10, 11, 12);
2837  CHECK(size == ryml::format(buf, "a={} foo {} {} bar {}", "this_is_a", 10, 11, 12));
2838  CHECK(size == strlen("a=this_is_a foo 10 11 bar 12"));
2839  // it is also safe to call on an insufficient buffer:
2840  char smallbuf[8] = {};
2841  size = ryml::format(smallbuf, "{} is too large {}", "this", "for the buffer");
2842  CHECK(size == strlen("this is too large for the buffer"));
2843  // ... and the result is truncated at the buffer size:
2844  CHECK(ryml::substr(smallbuf, sizeof(smallbuf)) == "this is\0");
2845 
2846  // format_sub() directly returns the written string:
2847  ryml::csubstr result = ryml::format_sub(buf, "b={}, damn it.", 1);
2848  CHECK(result == "b=1, damn it.");
2849  CHECK(result.is_sub(buf));
2850 
2851  // formatrs() means FORMAT & ReSize:
2852  //
2853  // Instead of a substr, it receives any owning linear char container
2854  // for which to_substr() is defined (using ADL).
2855  // <ryml_std.hpp> has to_substr() definitions for std::string and
2856  // std::vector<char>.
2857  //
2858  // formatrs() starts by calling format(), and if needed, resizes the container
2859  // and calls format() again.
2860  //
2861  // Note that unless the container is previously sized, this
2862  // may cause an allocation, which will make your code slower.
2863  // Make sure to call .reserve() on the container for real
2864  // production code.
2865  std::string sbuf;
2866  ryml::formatrs(&sbuf, "and c={} seems about right", 2);
2867  CHECK(sbuf == "and c=2 seems about right");
2868  std::vector<char> vbuf; // works with any linear char container
2869  ryml::formatrs(&vbuf, "and c={} seems about right", 2);
2870  CHECK(sbuf == "and c=2 seems about right");
2871  // with formatrs() it is also possible to append:
2872  ryml::formatrs_append(&sbuf, ", and finally d={} - done", 3);
2873  CHECK(sbuf == "and c=2 seems about right, and finally d=3 - done");
2874  }
2875 
2876  // unformat(): read arguments - opposite of format()
2877  {
2878  char buf_[256];
2879 
2880  int a = 0, b = 1, c = 2;
2881  ryml::csubstr result = ryml::format_sub(buf_, "{} and {} and {}", a, b, c);
2882  CHECK(result == "0 and 1 and 2");
2883  int aa = -1, bb = -2, cc = -3;
2884  size_t num_characters = ryml::unformat(result, "{} and {} and {}", aa, bb, cc);
2885  CHECK(num_characters != ryml::csubstr::npos); // if a conversion fails, returns ryml::csubstr::npos
2886  CHECK(num_characters == result.size());
2887  CHECK(aa == a);
2888  CHECK(bb == b);
2889  CHECK(cc == c);
2890 
2891  result = ryml::format_sub(buf_, "{} and {} and {}", 10, 20, 30);
2892  CHECK(result == "10 and 20 and 30");
2893  num_characters = ryml::unformat(result, "{} and {} and {}", aa, bb, cc);
2894  CHECK(num_characters != ryml::csubstr::npos); // if a conversion fails, returns ryml::csubstr::npos
2895  CHECK(num_characters == result.size());
2896  CHECK(aa == 10);
2897  CHECK(bb == 20);
2898  CHECK(cc == 30);
2899  }
2900 
2901  // cat(), cat_sub(), catrs(): concatenate arguments
2902  {
2903  char buf_[256] = {};
2904  ryml::substr buf = buf_;
2905  size_t size = ryml::cat(buf, "a=", 0.1, "foo", 10, 11, "bar", 12);
2906  CHECK(size == strlen("a=0.1foo1011bar12"));
2907  CHECK(buf.first(size) == "a=0.1foo1011bar12");
2908  // it is safe to call on an empty buffer:
2909  // returns the size needed for the result, and no overflow occurs:
2910  CHECK(ryml::cat({}, "a=", 0) == 3);
2911  // it is also safe to call on an insufficient buffer:
2912  char smallbuf[8] = {};
2913  size = ryml::cat(smallbuf, "this", " is too large ", "for the buffer");
2914  CHECK(size == strlen("this is too large for the buffer"));
2915  // ... and the result is truncated at the buffer size:
2916  CHECK(ryml::substr(smallbuf, sizeof(smallbuf)) == "this is\0");
2917 
2918  // cat_sub() directly returns the written string:
2919  ryml::csubstr result = ryml::cat_sub(buf, "b=", 1, ", damn it.");
2920  CHECK(result == "b=1, damn it.");
2921  CHECK(result.is_sub(buf));
2922 
2923  // catrs() means CAT & ReSize:
2924  //
2925  // Instead of a substr, it receives any owning linear char container
2926  // for which to_substr() is defined (using ADL).
2927  // <ryml_std.hpp> has to_substr() definitions for std::string and
2928  // std::vector<char>.
2929  //
2930  // catrs() starts by calling cat(), and if needed, resizes the container
2931  // and calls cat() again.
2932  //
2933  // Note that unless the container is previously sized, this
2934  // may cause an allocation, which will make your code slower.
2935  // Make sure to call .reserve() on the container for real
2936  // production code.
2937  std::string sbuf;
2938  ryml::catrs(&sbuf, "and c=", 2, " seems about right");
2939  CHECK(sbuf == "and c=2 seems about right");
2940  std::vector<char> vbuf; // works with any linear char container
2941  ryml::catrs(&vbuf, "and c=", 2, " seems about right");
2942  CHECK(sbuf == "and c=2 seems about right");
2943  // with catrs() it is also possible to append:
2944  ryml::catrs_append(&sbuf, ", and finally d=", 3, " - done");
2945  CHECK(sbuf == "and c=2 seems about right, and finally d=3 - done");
2946  }
2947 
2948  // uncat(): read arguments - opposite of cat()
2949  {
2950  char buf_[256];
2951 
2952  int a = 0, b = 1, c = 2;
2953  ryml::csubstr result = ryml::cat_sub(buf_, a, ' ', b, ' ', c);
2954  CHECK(result == "0 1 2");
2955  int aa = -1, bb = -2, cc = -3;
2956  char sep1 = 'a', sep2 = 'b';
2957  size_t num_characters = ryml::uncat(result, aa, sep1, bb, sep2, cc);
2958  CHECK(num_characters == result.size());
2959  CHECK(aa == a);
2960  CHECK(bb == b);
2961  CHECK(cc == c);
2962  CHECK(sep1 == ' ');
2963  CHECK(sep2 == ' ');
2964 
2965  result = ryml::cat_sub(buf_, 10, ' ', 20, ' ', 30);
2966  CHECK(result == "10 20 30");
2967  num_characters = ryml::uncat(result, aa, sep1, bb, sep2, cc);
2968  CHECK(num_characters == result.size());
2969  CHECK(aa == 10);
2970  CHECK(bb == 20);
2971  CHECK(cc == 30);
2972  CHECK(sep1 == ' ');
2973  CHECK(sep2 == ' ');
2974  }
2975 
2976  // catsep(), catsep_sub(), catseprs(): concatenate arguments, with a separator
2977  {
2978  char buf_[256] = {};
2979  ryml::substr buf = buf_;
2980  // use ' ' as a separator
2981  size_t size = ryml::catsep(buf, ' ', "a=", 0, "b=", 1, "c=", 2, 45, 67);
2982  CHECK(buf.first(size) == "a= 0 b= 1 c= 2 45 67");
2983  // any separator may be used
2984  // use " and " as a separator
2985  size = ryml::catsep(buf, " and ", "a=0", "b=1", "c=2", 45, 67);
2986  CHECK(buf.first(size) == "a=0 and b=1 and c=2 and 45 and 67");
2987  // use " ... " as a separator
2988  size = ryml::catsep(buf, " ... ", "a=0", "b=1", "c=2", 45, 67);
2989  CHECK(buf.first(size) == "a=0 ... b=1 ... c=2 ... 45 ... 67");
2990  // use '/' as a separator
2991  size = ryml::catsep(buf, '/', "a=", 0, "b=", 1, "c=", 2, 45, 67);
2992  CHECK(buf.first(size) == "a=/0/b=/1/c=/2/45/67");
2993  // use 888 as a separator
2994  size = ryml::catsep(buf, 888, "a=0", "b=1", "c=2", 45, 67);
2995  CHECK(buf.first(size) == "a=0888b=1888c=28884588867");
2996 
2997  // it is safe to call on an empty buffer:
2998  // returns the size needed for the result, and no overflow occurs:
2999  CHECK(size == ryml::catsep({}, 888, "a=0", "b=1", "c=2", 45, 67));
3000  // it is also safe to call on an insufficient buffer:
3001  char smallbuf[8] = {};
3002  CHECK(size == ryml::catsep(smallbuf, 888, "a=0", "b=1", "c=2", 45, 67));
3003  CHECK(size == strlen("a=0888b=1888c=28884588867"));
3004  // ... and the result is truncated:
3005  CHECK(ryml::substr(smallbuf, sizeof(smallbuf)) == "a=0888b\0");
3006 
3007  // catsep_sub() directly returns the written substr:
3008  ryml::csubstr result = ryml::catsep_sub(buf, " and ", "a=0", "b=1", "c=2", 45, 67);
3009  CHECK(result == "a=0 and b=1 and c=2 and 45 and 67");
3010  CHECK(result.is_sub(buf));
3011 
3012  // catseprs() means CATSEP & ReSize:
3013  //
3014  // Instead of a substr, it receives any owning linear char container
3015  // for which to_substr() is defined (using ADL).
3016  // <ryml_std.hpp> has to_substr() definitions for std::string and
3017  // std::vector<char>.
3018  //
3019  // catseprs() starts by calling catsep(), and if needed, resizes the container
3020  // and calls catsep() again.
3021  //
3022  // Note that unless the container is previously sized, this
3023  // may cause an allocation, which will make your code slower.
3024  // Make sure to call .reserve() on the container for real
3025  // production code.
3026  std::string sbuf;
3027  ryml::catseprs(&sbuf, " and ", "a=0", "b=1", "c=2", 45, 67);
3028  CHECK(sbuf == "a=0 and b=1 and c=2 and 45 and 67");
3029  std::vector<char> vbuf; // works with any linear char container
3030  ryml::catseprs(&vbuf, " and ", "a=0", "b=1", "c=2", 45, 67);
3031  CHECK(ryml::to_csubstr(vbuf) == "a=0 and b=1 and c=2 and 45 and 67");
3032 
3033  // with catseprs() it is also possible to append:
3034  ryml::catseprs_append(&sbuf, " well ", " --- a=0", "b=11", "c=12", 145, 167);
3035  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");
3036  }
3037 
3038  // uncatsep(): read arguments with a separator - opposite of catsep()
3039  {
3040  char buf_[256] = {};
3041 
3042  int a = 0, b = 1, c = 2;
3043  ryml::csubstr result = ryml::catsep_sub(buf_, ' ', a, b, c);
3044  CHECK(result == "0 1 2");
3045  int aa = -1, bb = -2, cc = -3;
3046  size_t num_characters = ryml::uncatsep(result, " ", aa, bb, cc);
3047  CHECK(num_characters == result.size());
3048  CHECK(aa == a);
3049  CHECK(bb == b);
3050  CHECK(cc == c);
3051 
3052  result = ryml::catsep_sub(buf_, "--", 10, 20, 30);
3053  CHECK(result == "10--20--30");
3054  num_characters = ryml::uncatsep(result, "--", aa, bb, cc);
3055  CHECK(num_characters == result.size());
3056  CHECK(aa == 10);
3057  CHECK(bb == 20);
3058  CHECK(cc == 30);
3059  }
3060 
3061  // formatting individual arguments
3062  {
3063  using namespace ryml; // all the symbols below are in the ryml namespace.
3064  char buf_[256] = {}; // all the results below are written in this buffer
3065  substr buf = buf_;
3066  // --------------------------------------
3067  // fmt::boolalpha(): format as true/false
3068  // --------------------------------------
3069  // just as with std streams, printing a bool will output the integer value:
3070  CHECK("0" == cat_sub(buf, false));
3071  CHECK("1" == cat_sub(buf, true));
3072  // to force a "true"/"false", use fmt::boolalpha:
3073  CHECK("false" == cat_sub(buf, fmt::boolalpha(false)));
3074  CHECK("true" == cat_sub(buf, fmt::boolalpha(true)));
3075 
3076  // ---------------------------------
3077  // fmt::hex(): format as hexadecimal
3078  // ---------------------------------
3079  CHECK("0xff" == cat_sub(buf, fmt::hex(255)));
3080  CHECK("0x100" == cat_sub(buf, fmt::hex(256)));
3081  CHECK("-0xff" == cat_sub(buf, fmt::hex(-255)));
3082  CHECK("-0x100" == cat_sub(buf, fmt::hex(-256)));
3083  CHECK("3735928559" == cat_sub(buf, UINT32_C(0xdeadbeef)));
3084  CHECK("0xdeadbeef" == cat_sub(buf, fmt::hex(UINT32_C(0xdeadbeef))));
3085  // ----------------------------
3086  // fmt::bin(): format as binary
3087  // ----------------------------
3088  CHECK("0b1000" == cat_sub(buf, fmt::bin(8)));
3089  CHECK("0b1001" == cat_sub(buf, fmt::bin(9)));
3090  CHECK("0b10001" == cat_sub(buf, fmt::bin(17)));
3091  CHECK("0b11001" == cat_sub(buf, fmt::bin(25)));
3092  CHECK("-0b1000" == cat_sub(buf, fmt::bin(-8)));
3093  CHECK("-0b1001" == cat_sub(buf, fmt::bin(-9)));
3094  CHECK("-0b10001" == cat_sub(buf, fmt::bin(-17)));
3095  CHECK("-0b11001" == cat_sub(buf, fmt::bin(-25)));
3096  // ---------------------------
3097  // fmt::bin(): format as octal
3098  // ---------------------------
3099  CHECK("0o77" == cat_sub(buf, fmt::oct(63)));
3100  CHECK("0o100" == cat_sub(buf, fmt::oct(64)));
3101  CHECK("0o377" == cat_sub(buf, fmt::oct(255)));
3102  CHECK("0o400" == cat_sub(buf, fmt::oct(256)));
3103  CHECK("0o1000" == cat_sub(buf, fmt::oct(512)));
3104  CHECK("-0o77" == cat_sub(buf, fmt::oct(-63)));
3105  CHECK("-0o100" == cat_sub(buf, fmt::oct(-64)));
3106  CHECK("-0o377" == cat_sub(buf, fmt::oct(-255)));
3107  CHECK("-0o400" == cat_sub(buf, fmt::oct(-256)));
3108  CHECK("-0o1000" == cat_sub(buf, fmt::oct(-512)));
3109  // ---------------------------
3110  // fmt::zpad(): pad with zeros
3111  // ---------------------------
3112  CHECK("000063" == cat_sub(buf, fmt::zpad(63, 6)));
3113  CHECK( "00063" == cat_sub(buf, fmt::zpad(63, 5)));
3114  CHECK( "0063" == cat_sub(buf, fmt::zpad(63, 4)));
3115  CHECK( "063" == cat_sub(buf, fmt::zpad(63, 3)));
3116  CHECK( "63" == cat_sub(buf, fmt::zpad(63, 2)));
3117  CHECK( "63" == cat_sub(buf, fmt::zpad(63, 1))); // will never trim the result
3118  CHECK( "63" == cat_sub(buf, fmt::zpad(63, 0))); // will never trim the result
3119  CHECK("0x00003f" == cat_sub(buf, fmt::zpad(fmt::hex(63), 6)));
3120  CHECK("0o000077" == cat_sub(buf, fmt::zpad(fmt::oct(63), 6)));
3121  CHECK("0b00011001" == cat_sub(buf, fmt::zpad(fmt::bin(25), 8)));
3122  // ------------------------------------------------
3123  // fmt::left(): align left with a given field width
3124  // ------------------------------------------------
3125  CHECK("63 " == cat_sub(buf, fmt::left(63, 6)));
3126  CHECK("63 " == cat_sub(buf, fmt::left(63, 5)));
3127  CHECK("63 " == cat_sub(buf, fmt::left(63, 4)));
3128  CHECK("63 " == cat_sub(buf, fmt::left(63, 3)));
3129  CHECK("63" == cat_sub(buf, fmt::left(63, 2)));
3130  CHECK("63" == cat_sub(buf, fmt::left(63, 1))); // will never trim the result
3131  CHECK("63" == cat_sub(buf, fmt::left(63, 0))); // will never trim the result
3132  // the fill character can be specified (defaults to ' '):
3133  CHECK("63----" == cat_sub(buf, fmt::left(63, 6, '-')));
3134  CHECK("63++++" == cat_sub(buf, fmt::left(63, 6, '+')));
3135  CHECK("63////" == cat_sub(buf, fmt::left(63, 6, '/')));
3136  CHECK("630000" == cat_sub(buf, fmt::left(63, 6, '0')));
3137  CHECK("63@@@@" == cat_sub(buf, fmt::left(63, 6, '@')));
3138  CHECK("0x003f " == cat_sub(buf, fmt::left(fmt::zpad(fmt::hex(63), 4), 10)));
3139  // --------------------------------------------------
3140  // fmt::right(): align right with a given field width
3141  // --------------------------------------------------
3142  CHECK(" 63" == cat_sub(buf, fmt::right(63, 6)));
3143  CHECK(" 63" == cat_sub(buf, fmt::right(63, 5)));
3144  CHECK(" 63" == cat_sub(buf, fmt::right(63, 4)));
3145  CHECK(" 63" == cat_sub(buf, fmt::right(63, 3)));
3146  CHECK("63" == cat_sub(buf, fmt::right(63, 2)));
3147  CHECK("63" == cat_sub(buf, fmt::right(63, 1))); // will never trim the result
3148  CHECK("63" == cat_sub(buf, fmt::right(63, 0))); // will never trim the result
3149  // the fill character can be specified (defaults to ' '):
3150  CHECK("----63" == cat_sub(buf, fmt::right(63, 6, '-')));
3151  CHECK("++++63" == cat_sub(buf, fmt::right(63, 6, '+')));
3152  CHECK("////63" == cat_sub(buf, fmt::right(63, 6, '/')));
3153  CHECK("000063" == cat_sub(buf, fmt::right(63, 6, '0')));
3154  CHECK("@@@@63" == cat_sub(buf, fmt::right(63, 6, '@')));
3155  CHECK(" 0x003f" == cat_sub(buf, fmt::right(fmt::zpad(fmt::hex(63), 4), 10)));
3156 
3157  // ------------------------------------------
3158  // fmt::real(): format floating point numbers
3159  // ------------------------------------------
3160  // see also sample_float_precision()
3161  CHECK("0" == cat_sub(buf, fmt::real(0.01f, 0)));
3162  CHECK("0.0" == cat_sub(buf, fmt::real(0.01f, 1)));
3163  CHECK("0.01" == cat_sub(buf, fmt::real(0.01f, 2)));
3164  CHECK("0.010" == cat_sub(buf, fmt::real(0.01f, 3)));
3165  CHECK("0.0100" == cat_sub(buf, fmt::real(0.01f, 4)));
3166  CHECK("0.01000" == cat_sub(buf, fmt::real(0.01f, 5)));
3167  CHECK("1" == cat_sub(buf, fmt::real(1.01f, 0)));
3168  CHECK("1.0" == cat_sub(buf, fmt::real(1.01f, 1)));
3169  CHECK("1.01" == cat_sub(buf, fmt::real(1.01f, 2)));
3170  CHECK("1.010" == cat_sub(buf, fmt::real(1.01f, 3)));
3171  CHECK("1.0100" == cat_sub(buf, fmt::real(1.01f, 4)));
3172  CHECK("1.01000" == cat_sub(buf, fmt::real(1.01f, 5)));
3173  CHECK("1" == cat_sub(buf, fmt::real(1.234234234, 0)));
3174  CHECK("1.2" == cat_sub(buf, fmt::real(1.234234234, 1)));
3175  CHECK("1.23" == cat_sub(buf, fmt::real(1.234234234, 2)));
3176  CHECK("1.234" == cat_sub(buf, fmt::real(1.234234234, 3)));
3177  CHECK("1.2342" == cat_sub(buf, fmt::real(1.234234234, 4)));
3178  CHECK("1.23423" == cat_sub(buf, fmt::real(1.234234234, 5)));
3179  CHECK("1000000.00000" == cat_sub(buf, fmt::real(1000000.000000000, 5)));
3180  CHECK("1234234.23423" == cat_sub(buf, fmt::real(1234234.234234234, 5)));
3181  // AKA %f
3182  CHECK("1000000.00000" == cat_sub(buf, fmt::real(1000000.000000000, 5, FTOA_FLOAT))); // AKA %f, same as above
3183  CHECK("1234234.23423" == cat_sub(buf, fmt::real(1234234.234234234, 5, FTOA_FLOAT))); // AKA %f
3184  CHECK("1234234.2342" == cat_sub(buf, fmt::real(1234234.234234234, 4, FTOA_FLOAT))); // AKA %f
3185  CHECK("1234234.234" == cat_sub(buf, fmt::real(1234234.234234234, 3, FTOA_FLOAT))); // AKA %f
3186  CHECK("1234234.23" == cat_sub(buf, fmt::real(1234234.234234234, 2, FTOA_FLOAT))); // AKA %f
3187  // AKA %e
3188  CHECK("1.00000e+06" == cat_sub(buf, fmt::real(1000000.000000000, 5, FTOA_SCIENT))); // AKA %e
3189  CHECK("1.23423e+06" == cat_sub(buf, fmt::real(1234234.234234234, 5, FTOA_SCIENT))); // AKA %e
3190  CHECK("1.2342e+06" == cat_sub(buf, fmt::real(1234234.234234234, 4, FTOA_SCIENT))); // AKA %e
3191  CHECK("1.234e+06" == cat_sub(buf, fmt::real(1234234.234234234, 3, FTOA_SCIENT))); // AKA %e
3192  CHECK("1.23e+06" == cat_sub(buf, fmt::real(1234234.234234234, 2, FTOA_SCIENT))); // AKA %e
3193  // AKA %g
3194  CHECK("1e+06" == cat_sub(buf, fmt::real(1000000.000000000, 5, FTOA_FLEX))); // AKA %g
3195  CHECK("1.2342e+06" == cat_sub(buf, fmt::real(1234234.234234234, 5, FTOA_FLEX))); // AKA %g
3196  CHECK("1.234e+06" == cat_sub(buf, fmt::real(1234234.234234234, 4, FTOA_FLEX))); // AKA %g
3197  CHECK("1.23e+06" == cat_sub(buf, fmt::real(1234234.234234234, 3, FTOA_FLEX))); // AKA %g
3198  CHECK("1.2e+06" == cat_sub(buf, fmt::real(1234234.234234234, 2, FTOA_FLEX))); // AKA %g
3199  // FTOA_HEXA: AKA %a (hexadecimal formatting of floats)
3200  CHECK("0x1.e8480p+19" == cat_sub(buf, fmt::real(1000000.000000000, 5, FTOA_HEXA))); // AKA %a
3201  CHECK("0x1.2d53ap+20" == cat_sub(buf, fmt::real(1234234.234234234, 5, FTOA_HEXA))); // AKA %a
3202 
3203  // --------------------------------------------------------------
3204  // fmt::raw(): dump data in raw (binary) machine format (respecting alignment)
3205  // --------------------------------------------------------------
3206  {
3207  C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH("-Wcast-align") // we're casting the values directly, so alignment is strictly respected.
3208  const uint32_t payload[] = {10, 20, 30, 40, UINT32_C(0xdeadbeef)};
3209  // (package payload as a substr, for comparison only)
3210  csubstr expected = csubstr((const char *)payload, sizeof(payload));
3211  csubstr actual = cat_sub(buf, fmt::raw(payload));
3212  CHECK(!actual.overlaps(expected));
3213  CHECK(0 == memcmp(expected.str, actual.str, expected.len));
3214  // also possible with variables:
3215  for(const uint32_t value : payload)
3216  {
3217  // (package payload as a substr, for comparison only)
3218  expected = csubstr((const char *)&value, sizeof(value));
3219  actual = cat_sub(buf, fmt::raw(value));
3220  CHECK(actual.size() == sizeof(uint32_t));
3221  CHECK(!actual.overlaps(expected));
3222  CHECK(0 == memcmp(expected.str, actual.str, expected.len));
3223  // with non-const data, fmt::craw() may be needed for disambiguation:
3224  actual = cat_sub(buf, fmt::craw(value));
3225  CHECK(actual.size() == sizeof(uint32_t));
3226  CHECK(!actual.overlaps(expected));
3227  CHECK(0 == memcmp(expected.str, actual.str, expected.len));
3228  //
3229  // read back:
3230  uint32_t result = 0;
3231  auto reader = fmt::raw(result);
3232  CHECK(uncat(actual, reader));
3233  // and compare:
3234  // (vs2017/release/32bit does not reload result from cache, so force it)
3235  CHECK(result == value); // roundtrip completed successfully
3236  }
3237  C4_SUPPRESS_WARNING_GCC_CLANG_POP
3238  }
3239 
3240  // -------------------------
3241  // fmt::base64(): see below!
3242  // -------------------------
3243  }
3244 }
left_< T > left(T val, size_t width, char padchar=' ')
tag type to mark an argument to be aligned left.
Definition: format.hpp:519
right_< T > right(T val, size_t width, char padchar=' ')
tag function to mark an argument to be aligned right
Definition: format.hpp:557
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:1113
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:653
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:1044
substr cat_sub(substr buf, Args const &...args)
like c4::cat() but return a substr instead of a size
Definition: format.hpp:663
csubstr catseprs_append(CharOwningContainer *cont, Sep const &sep, Args const &...args)
catsep+resize+append: like c4::catsep(), but receives a container, and appends the arguments,...
Definition: format.hpp:1224
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:1156
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:778
substr catsep_sub(substr buf, Args &&...args)
like c4::catsep() but return a substr instead of a size
Definition: format.hpp:790
@ 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 c4::format(), but receives a container, and appends the arguments,...
Definition: format.hpp:1334
substr format_sub(substr buf, csubstr fmt, Args const &...args)
like c4::format() but return a substr instead of a size
Definition: format.hpp:960
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:940
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:1267
integral_< intptr_t > hex(std::nullptr_t)
format null as an hexadecimal value
Definition: format.hpp:160
integral_< intptr_t > bin(std::nullptr_t)
format null as a binary 0-1 value
Definition: format.hpp:212
integral_< intptr_t > oct(std::nullptr_t)
format null as an octal value
Definition: format.hpp:185
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:422
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:416
size_t uncat(csubstr buf, Arg &a, Args &...more)
deserialize the arguments from the given buffer.
Definition: format.hpp:694
size_t uncatsep(csubstr buf, csubstr sep, Arg &a, Args &...more)
deserialize the arguments from the given buffer, using a separator.
Definition: format.hpp:823
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:991
integral_padded_< T > zpad(T val, size_t num_digits)
pad the argument with zeroes on the left, with decimal radix
Definition: format.hpp:232

◆ sample_base64()

void sample_base64 ( )

demonstrates how to read and write base64-encoded blobs.

See also
Base64 encoding/decoding

Definition at line 3251 of file quickstart.cpp.

3252 {
3253  ryml::Tree tree;
3254  tree.rootref() |= ryml::MAP;
3255  struct text_and_base64 { ryml::csubstr text, base64; };
3256  text_and_base64 cases[] = {
3257  {{"Hello, World!"}, {"SGVsbG8sIFdvcmxkIQ=="}},
3258  {{"Brevity is the soul of wit."}, {"QnJldml0eSBpcyB0aGUgc291bCBvZiB3aXQu"}},
3259  {{"All that glitters is not gold."}, {"QWxsIHRoYXQgZ2xpdHRlcnMgaXMgbm90IGdvbGQu"}},
3260  };
3261  // to encode base64 and write the result to val:
3262  for(text_and_base64 c : cases)
3263  {
3264  tree[c.text] << ryml::fmt::base64(c.text);
3265  CHECK(tree[c.text].val() == c.base64);
3266  }
3267  // to encode base64 and write the result to key:
3268  for(text_and_base64 c : cases)
3269  {
3270  tree.rootref().append_child() << ryml::key(ryml::fmt::base64(c.text)) << c.text;
3271  CHECK(tree[c.base64].val() == c.text);
3272  }
3273  CHECK(ryml::emitrs_yaml<std::string>(tree) == ""
3274  "Hello, World!: SGVsbG8sIFdvcmxkIQ==" "\n"
3275  "Brevity is the soul of wit.: QnJldml0eSBpcyB0aGUgc291bCBvZiB3aXQu" "\n"
3276  "All that glitters is not gold.: QWxsIHRoYXQgZ2xpdHRlcnMgaXMgbm90IGdvbGQu" "\n"
3277  "SGVsbG8sIFdvcmxkIQ==: Hello, World!" "\n"
3278  "QnJldml0eSBpcyB0aGUgc291bCBvZiB3aXQu: Brevity is the soul of wit." "\n"
3279  "QWxsIHRoYXQgZ2xpdHRlcnMgaXMgbm90IGdvbGQu: All that glitters is not gold." "\n"
3280  "");
3281  char buf1_[128], buf2_[128];
3282  ryml::substr buf1 = buf1_; // this is where we will write the result (using >>)
3283  ryml::substr buf2 = buf2_; // this is where we will write the result (using deserialize_val()/deserialize_key())
3284  std::string result = {}; // show also how to decode to a std::string
3285  // to decode the val base64 and write the result to buf:
3286  for(const text_and_base64 c : cases)
3287  {
3288  // write the decoded result into the given buffer
3289  tree[c.text] >> ryml::fmt::base64(buf1); // cannot know the needed size
3290  size_t len = tree[c.text].deserialize_val(ryml::fmt::base64(buf2)); // returns the needed size
3291  CHECK(len <= buf1.len);
3292  CHECK(len <= buf2.len);
3293  CHECK(c.text.len == len);
3294  CHECK(buf1.first(len) == c.text);
3295  CHECK(buf2.first(len) == c.text);
3296  //
3297  // interop with std::string: using substr
3298  result.clear(); // this is not needed. We do it just to show that the first call can fail.
3299  len = tree[c.text].deserialize_val(ryml::fmt::base64(ryml::to_substr(result))); // returns the needed size
3300  if(len > result.size()) // the size was not enough; resize and call again
3301  {
3302  result.resize(len);
3303  len = tree[c.text].deserialize_val(ryml::fmt::base64(ryml::to_substr(result))); // returns the needed size
3304  }
3305  result.resize(len); // trim to the length of the decoded buffer
3306  CHECK(result == c.text);
3307  //
3308  // interop with std::string: using blob
3309  result.clear(); // this is not needed. We do it just to show that the first call can fail.
3310  ryml::blob strblob(&result[0], result.size());
3311  CHECK(strblob.buf == result.data());
3312  CHECK(strblob.len == result.size());
3313  len = tree[c.text].deserialize_val(ryml::fmt::base64(strblob)); // returns the needed size
3314  if(len > result.size()) // the size was not enough; resize and call again
3315  {
3316  result.resize(len);
3317  strblob = {&result[0], result.size()};
3318  CHECK(strblob.buf == result.data());
3319  CHECK(strblob.len == result.size());
3320  len = tree[c.text].deserialize_val(ryml::fmt::base64(strblob)); // returns the needed size
3321  }
3322  result.resize(len); // trim to the length of the decoded buffer
3323  CHECK(result == c.text);
3324  //
3325  // Note also these are just syntatic wrappers to simplify client code.
3326  // You can call into the lower level functions without much effort:
3327  result.clear(); // this is not needed. We do it just to show that the first call can fail.
3328  ryml::csubstr encoded = tree[c.text].val();
3329  CHECK(encoded == c.base64);
3330  len = base64_decode(encoded, ryml::blob{&result[0], result.size()});
3331  if(len > result.size()) // the size was not enough; resize and call again
3332  {
3333  result.resize(len);
3334  len = base64_decode(encoded, ryml::blob{&result[0], result.size()});
3335  }
3336  result.resize(len); // trim to the length of the decoded buffer
3337  CHECK(result == c.text);
3338  }
3339  // to decode the key base64 and write the result to buf:
3340  for(const text_and_base64 c : cases)
3341  {
3342  // write the decoded result into the given buffer
3343  tree[c.base64] >> ryml::key(ryml::fmt::base64(buf1)); // cannot know the needed size
3344  size_t len = tree[c.base64].deserialize_key(ryml::fmt::base64(buf2)); // returns the needed size
3345  CHECK(len <= buf1.len);
3346  CHECK(len <= buf2.len);
3347  CHECK(c.text.len == len);
3348  CHECK(buf1.first(len) == c.text);
3349  CHECK(buf2.first(len) == c.text);
3350  // interop with std::string: using substr
3351  result.clear(); // this is not needed. We do it just to show that the first call can fail.
3352  len = tree[c.base64].deserialize_key(ryml::fmt::base64(ryml::to_substr(result))); // returns the needed size
3353  if(len > result.size()) // the size was not enough; resize and call again
3354  {
3355  result.resize(len);
3356  len = tree[c.base64].deserialize_key(ryml::fmt::base64(ryml::to_substr(result))); // returns the needed size
3357  }
3358  result.resize(len); // trim to the length of the decoded buffer
3359  CHECK(result == c.text);
3360  //
3361  // interop with std::string: using blob
3362  result.clear(); // this is not needed. We do it just to show that the first call can fail.
3363  ryml::blob strblob = {&result[0], result.size()};
3364  CHECK(strblob.buf == result.data());
3365  CHECK(strblob.len == result.size());
3366  len = tree[c.base64].deserialize_key(ryml::fmt::base64(strblob)); // returns the needed size
3367  if(len > result.size()) // the size was not enough; resize and call again
3368  {
3369  result.resize(len);
3370  strblob = {&result[0], result.size()};
3371  CHECK(strblob.buf == result.data());
3372  CHECK(strblob.len == result.size());
3373  len = tree[c.base64].deserialize_key(ryml::fmt::base64(strblob)); // returns the needed size
3374  }
3375  result.resize(len); // trim to the length of the decoded buffer
3376  CHECK(result == c.text);
3377  //
3378  // Note also these are just syntactic wrappers to simplify client code.
3379  // You can call into the lower level functions without much effort:
3380  result.clear(); // this is not needed. We do it just to show that the first call can fail.
3381  ryml::csubstr encoded = tree[c.base64].key();
3382  CHECK(encoded == c.base64);
3383  len = base64_decode(encoded, ryml::blob{&result[0], result.size()});
3384  if(len > result.size()) // the size was not enough; resize and call again
3385  {
3386  result.resize(len);
3387  len = base64_decode(encoded, ryml::blob{&result[0], result.size()});
3388  }
3389  result.resize(len); // trim to the length of the decoded buffer
3390  CHECK(result == c.text);
3391  }
3392  // directly encode variables
3393  {
3394  const uint64_t valin = UINT64_C(0xdeadbeef);
3395  uint64_t valout = 0;
3396  tree["deadbeef"] << c4::fmt::base64(valin); // sometimes cbase64() is needed to avoid ambiguity
3397  size_t len = tree["deadbeef"].deserialize_val(ryml::fmt::base64(valout));
3398  CHECK(len <= sizeof(valout));
3399  CHECK(valout == UINT64_C(0xdeadbeef)); // base64 roundtrip is bit-accurate
3400  }
3401  // directly encode memory ranges
3402  {
3403  const uint32_t data_in[11] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xdeadbeef};
3404  uint32_t data_out[11] = {};
3405  CHECK(memcmp(data_in, data_out, sizeof(data_in)) != 0); // before the roundtrip
3406  tree["int_data"] << c4::fmt::base64(data_in);
3407  size_t len = tree["int_data"].deserialize_val(ryml::fmt::base64(data_out));
3408  CHECK(len <= sizeof(data_out));
3409  CHECK(memcmp(data_in, data_out, sizeof(data_in)) == 0); // after the roundtrip
3410  }
3411 }
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

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

3634 {
3635  ryml::Tree t;
3636 
3637  auto r = t.rootref();
3638  r |= ryml::MAP;
3639 
3640  vec2<int> v2in{10, 11};
3641  vec2<int> v2out{1, 2};
3642  r["v2"] << v2in; // serializes to the tree's arena, and then sets the keyval
3643  r["v2"] >> v2out;
3644  CHECK(v2in.x == v2out.x);
3645  CHECK(v2in.y == v2out.y);
3646  vec3<int> v3in{100, 101, 102};
3647  vec3<int> v3out{1, 2, 3};
3648  r["v3"] << v3in; // serializes to the tree's arena, and then sets the keyval
3649  r["v3"] >> v3out;
3650  CHECK(v3in.x == v3out.x);
3651  CHECK(v3in.y == v3out.y);
3652  CHECK(v3in.z == v3out.z);
3653  vec4<int> v4in{1000, 1001, 1002, 1003};
3654  vec4<int> v4out{1, 2, 3, 4};
3655  r["v4"] << v4in; // serializes to the tree's arena, and then sets the keyval
3656  r["v4"] >> v4out;
3657  CHECK(v4in.x == v4out.x);
3658  CHECK(v4in.y == v4out.y);
3659  CHECK(v4in.z == v4out.z);
3660  CHECK(v4in.w == v4out.w);
3661  CHECK(ryml::emitrs_yaml<std::string>(t) == ""
3662  "v2: (10,11)" "\n"
3663  "v3: (100,101,102)" "\n"
3664  "v4: (1000,1001,1002,1003)" "\n"
3665  "");
3666 
3667  // note that only the used functions are needed:
3668  // - if a type is only parsed, then only from_chars() is needed
3669  // - if a type is only emitted, then only to_chars() is needed
3670  emit_only_vec2<int> eov2in{20, 21}; // only has to_chars()
3671  parse_only_vec2<int> pov2out{1, 2}; // only has from_chars()
3672  r["v2"] << eov2in; // serializes to the tree's arena, and then sets the keyval
3673  r["v2"] >> pov2out;
3674  CHECK(eov2in.x == pov2out.x);
3675  CHECK(eov2in.y == pov2out.y);
3676  emit_only_vec3<int> eov3in{30, 31, 32}; // only has to_chars()
3677  parse_only_vec3<int> pov3out{1, 2, 3}; // only has from_chars()
3678  r["v3"] << eov3in; // serializes to the tree's arena, and then sets the keyval
3679  r["v3"] >> pov3out;
3680  CHECK(eov3in.x == pov3out.x);
3681  CHECK(eov3in.y == pov3out.y);
3682  CHECK(eov3in.z == pov3out.z);
3683  emit_only_vec4<int> eov4in{40, 41, 42, 43}; // only has to_chars()
3684  parse_only_vec4<int> pov4out{1, 2, 3, 4}; // only has from_chars()
3685  r["v4"] << eov4in; // serializes to the tree's arena, and then sets the keyval
3686  r["v4"] >> pov4out;
3687  CHECK(eov4in.x == pov4out.x);
3688  CHECK(eov4in.y == pov4out.y);
3689  CHECK(eov4in.z == pov4out.z);
3690  CHECK(ryml::emitrs_yaml<std::string>(t) == ""
3691  "v2: (20,21)" "\n"
3692  "v3: (30,31,32)" "\n"
3693  "v4: (40,41,42,43)" "\n"
3694  "");
3695 }
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

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

3832 {
3833  my_type mt_in{
3834  {20, 21},
3835  {30, 31, 32},
3836  {40, 41, 42, 43},
3837  {{101, 102, 103, 104, 105, 106, 107}},
3838  {{{1001, 2001}, {1002, 2002}, {1003, 2003}}},
3839  };
3840  my_type mt_out;
3841 
3842  ryml::Tree t;
3843  t.rootref() << mt_in; // read from this
3844  t.crootref() >> mt_out; // assign here
3845  CHECK(mt_out.v2.x == mt_in.v2.x);
3846  CHECK(mt_out.v2.y == mt_in.v2.y);
3847  CHECK(mt_out.v3.x == mt_in.v3.x);
3848  CHECK(mt_out.v3.y == mt_in.v3.y);
3849  CHECK(mt_out.v3.z == mt_in.v3.z);
3850  CHECK(mt_out.v4.x == mt_in.v4.x);
3851  CHECK(mt_out.v4.y == mt_in.v4.y);
3852  CHECK(mt_out.v4.z == mt_in.v4.z);
3853  CHECK(mt_out.v4.w == mt_in.v4.w);
3854  CHECK(mt_in.seq.seq_member.size() > 0);
3855  CHECK(mt_out.seq.seq_member.size() == mt_in.seq.seq_member.size());
3856  for(size_t i = 0; i < mt_in.seq.seq_member.size(); ++i)
3857  {
3858  CHECK(mt_out.seq.seq_member[i] == mt_in.seq.seq_member[i]);
3859  }
3860  CHECK(mt_in.map.map_member.size() > 0);
3861  CHECK(mt_out.map.map_member.size() == mt_in.map.map_member.size());
3862  for(auto const& kv : mt_in.map.map_member)
3863  {
3864  CHECK(mt_out.map.map_member.find(kv.first) != mt_out.map.map_member.end());
3865  CHECK(mt_out.map.map_member[kv.first] == kv.second);
3866  }
3867  CHECK(ryml::emitrs_yaml<std::string>(t) == ""
3868  "v2: (20,21)" "\n"
3869  "v3: (30,31,32)" "\n"
3870  "v4: (40,41,42,43)" "\n"
3871  "seq:" "\n"
3872  " - 101" "\n"
3873  " - 102" "\n"
3874  " - 103" "\n"
3875  " - 104" "\n"
3876  " - 105" "\n"
3877  " - 106" "\n"
3878  " - 107" "\n"
3879  "map:" "\n"
3880  " 1001: 2001" "\n"
3881  " 1002: 2002" "\n"
3882  " 1003: 2003" "\n"
3883  "");
3884 }
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

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

3894 {
3895  std::string yml_std_string = ""
3896  "- v2: (20,21)" "\n"
3897  " v3: (30,31,32)" "\n"
3898  " v4: (40,41,42,43)" "\n"
3899  " seq:" "\n"
3900  " - 101" "\n"
3901  " - 102" "\n"
3902  " - 103" "\n"
3903  " - 104" "\n"
3904  " - 105" "\n"
3905  " - 106" "\n"
3906  " - 107" "\n"
3907  " map:" "\n"
3908  " 1001: 2001" "\n"
3909  " 1002: 2002" "\n"
3910  " 1003: 2003" "\n"
3911  "- v2: (120,121)" "\n"
3912  " v3: (130,131,132)" "\n"
3913  " v4: (140,141,142,143)" "\n"
3914  " seq:" "\n"
3915  " - 1101" "\n"
3916  " - 1102" "\n"
3917  " - 1103" "\n"
3918  " - 1104" "\n"
3919  " - 1105" "\n"
3920  " - 1106" "\n"
3921  " - 1107" "\n"
3922  " map:" "\n"
3923  " 11001: 12001" "\n"
3924  " 11002: 12002" "\n"
3925  " 11003: 12003" "\n"
3926  "- v2: (220,221)" "\n"
3927  " v3: (230,231,232)" "\n"
3928  " v4: (240,241,242,243)" "\n"
3929  " seq:" "\n"
3930  " - 2101" "\n"
3931  " - 2102" "\n"
3932  " - 2103" "\n"
3933  " - 2104" "\n"
3934  " - 2105" "\n"
3935  " - 2106" "\n"
3936  " - 2107" "\n"
3937  " map:" "\n"
3938  " 21001: 22001" "\n"
3939  " 21002: 22002" "\n"
3940  " 21003: 22003" "\n"
3941  "";
3942  // parse in-place using the std::string above
3943  ryml::Tree tree = ryml::parse_in_place(ryml::to_substr(yml_std_string));
3944  // my_type is a container-of-containers type. see above its
3945  // definition implementation for ryml.
3946  std::vector<my_type> vmt;
3947  tree.rootref() >> vmt;
3948  CHECK(vmt.size() == 3);
3949  ryml::Tree tree_out;
3950  tree_out.rootref() << vmt;
3951  CHECK(ryml::emitrs_yaml<std::string>(tree_out) == yml_std_string);
3952 }

◆ sample_float_precision()

void sample_float_precision ( )

control precision of serialized floats

Definition at line 3958 of file quickstart.cpp.

3959 {
3960  std::vector<double> reference{1.23234412342131234, 2.12323123143434237, 3.67847983572591234};
3961  // A safe precision for comparing doubles. May vary depending on
3962  // compiler flags. Double goes to about 15 digits, so 14 should be
3963  // safe enough for this test to succeed.
3964  const double precision_safe = 1.e-14;
3965  const size_t num_digits_safe = 14;
3966  const size_t num_digits_original = 17;
3967  auto get_num_digits = [](ryml::csubstr number){ return number.len - 2u; };
3968  //
3969  // no significant precision is lost when reading
3970  // floating point numbers:
3971  {
3972  ryml::Tree tree = ryml::parse_in_arena("[1.23234412342131234, 2.12323123143434237, 3.67847983572591234]");
3973  std::vector<double> output;
3974  tree.rootref() >> output;
3975  CHECK(output.size() == reference.size());
3976  for(size_t i = 0; i < reference.size(); ++i)
3977  {
3978  CHECK(get_num_digits(tree[(ryml::id_type)i].val()) == num_digits_original);
3979  CHECK(fabs(output[i] - reference[i]) < precision_safe);
3980  }
3981  }
3982  //
3983  // However, depending on the compilation settings, there may be a
3984  // significant precision loss when serializing with the default
3985  // approach, operator<<(double):
3986  {
3987  ryml::Tree serialized;
3988  serialized.rootref() << reference;
3989  std::cout << serialized;
3990  // Without std::to_chars() there is a loss of precision:
3991  #if (!C4CORE_HAVE_STD_TOCHARS) // check if std::to_chars() is available.
3992  CHECK((ryml::emitrs_yaml<std::string>(serialized) == ""
3993  "- 1.23234" "\n"
3994  "- 2.12323" "\n"
3995  "- 3.67848" "\n"
3996  "") || (bool)"this is indicative; the exact results will vary from platform to platform.");
3997  C4_UNUSED(num_digits_safe);
3998  // ... but when using C++17 and above, the results are eminently equal:
3999  #else
4000  CHECK((ryml::emitrs_yaml<std::string>(serialized) == ""
4001  "- 1.2323441234213124" "\n"
4002  "- 2.1232312314343424" "\n"
4003  "- 3.6784798357259123" "\n"
4004  "") || (bool)"this is indicative; the exact results will vary from platform to platform.");
4005  size_t pos = 0;
4006  for(ryml::ConstNodeRef child : serialized.rootref().children())
4007  {
4008  CHECK(get_num_digits(child.val()) >= num_digits_safe);
4009  double out = {};
4010  child >> out;
4011  CHECK(fabs(out - reference[pos++]) < precision_safe);
4012  }
4013  #endif
4014  }
4015  //
4016  // The difference is explained by the availability of
4017  // fastfloat::from_chars(), std::from_chars() and std::to_chars().
4018  //
4019  // ryml prefers the fastfloat::from_chars() version. Unfortunately
4020  // fastfloat does not have to_chars() (see
4021  // https://github.com/fastfloat/fast_float/issues/23).
4022  //
4023  // When C++17 is used, ryml uses std::to_chars(), which produces
4024  // good defaults.
4025  //
4026  // However, with earlier standards, or in some library
4027  // implementations, there's only snprintf() available. Every other
4028  // std library function will either disrespect the string limits,
4029  // or more precisely, accept no string size limits. So the
4030  // implementation of c4core (which ryml uses) falls back to
4031  // snprintf("%g"), and that picks by default a (low) number of
4032  // digits.
4033  //
4034  // But all is not lost for C++11/C++14 users!
4035  //
4036  // To force a particular precision when serializing, you can use
4037  // c4::fmt::real() (brought into the ryml:: namespace). Or you can
4038  // serialize the number yourself! The small downside is that you
4039  // have to build the container.
4040  //
4041  // First a function to check the result:
4042  auto check_precision = [&](ryml::Tree const& serialized){
4043  std::cout << serialized;
4044  // now it works!
4045  CHECK((ryml::emitrs_yaml<std::string>(serialized) == ""
4046  "- 1.23234412342131239" "\n"
4047  "- 2.12323123143434245" "\n"
4048  "- 3.67847983572591231" "\n"
4049  "") || (bool)"this is indicative; the exact results will vary from platform to platform.");
4050  size_t pos = 0;
4051  for(ryml::ConstNodeRef child : serialized.rootref().children())
4052  {
4053  CHECK(get_num_digits(child.val()) == num_digits_original);
4054  double out = {};
4055  child >> out;
4056  CHECK(fabs(out - reference[pos++]) < precision_safe);
4057  }
4058  };
4059  //
4060  // Serialization example using fmt::real()
4061  {
4062  ryml::Tree serialized;
4063  ryml::NodeRef root = serialized.rootref();
4064  root |= ryml::SEQ;
4065  for(const double v : reference)
4066  root.append_child() << ryml::fmt::real(v, num_digits_original, ryml::FTOA_FLOAT);
4067  check_precision(serialized); // OK - now within bounds!
4068  }
4069  //
4070  // Serialization example using snprintf
4071  {
4072  ryml::Tree serialized;
4073  ryml::NodeRef root = serialized.rootref();
4074  root |= ryml::SEQ;
4075  char tmp[64];
4076  for(const double v : reference)
4077  {
4078  // reuse a buffer to serialize.
4079  // add 1 to the significant digits because the %g
4080  // specifier counts the integral digits.
4081  (void)snprintf(tmp, sizeof(tmp), "%.18g", v);
4082  // copy the serialized string to the tree (operator<<
4083  // copies to the arena, operator= just assigns the string
4084  // pointer and would be wrong in this case):
4085  root.append_child() << ryml::to_csubstr((const char*)tmp);
4086  }
4087  check_precision(serialized); // OK - now within bounds!
4088  }
4089 }

◆ sample_emit_to_container()

void sample_emit_to_container ( )

demonstrates how to emit to a linear container of char

Definition at line 4095 of file quickstart.cpp.

4096 {
4097 
4098  ryml::csubstr ymla =
4099  "- 1\n"
4100  "- 2\n"
4101  "";
4102  ryml::csubstr ymlb =
4103  "- a" "\n"
4104  "- b" "\n"
4105  "- x0: 1" "\n"
4106  " x1: 2" "\n"
4107  "- champagne: Dom Perignon" "\n"
4108  " coffee: Arabica" "\n"
4109  " more:" "\n"
4110  " vinho verde: Soalheiro" "\n"
4111  " vinho tinto: Redoma 2017" "\n"
4112  " beer:" "\n"
4113  " - Rochefort 10" "\n"
4114  " - Busch" "\n"
4115  " - Leffe Rituel" "\n"
4116  "- foo" "\n"
4117  "- bar" "\n"
4118  "- baz" "\n"
4119  "- bat" "\n"
4120  "";
4121  const ryml::Tree treea = ryml::parse_in_arena(ymla);
4122  const ryml::Tree treeb = ryml::parse_in_arena(ymlb);
4123 
4124  // it is possible to emit to any linear container of char.
4125  //
4126  // eg, std::vector<char>
4127  {
4128  // do a blank call on an empty buffer to find the required size.
4129  // no overflow will occur, and returns a substr with the size
4130  // required to output
4131  ryml::csubstr output = ryml::emit_yaml(treea, treea.root_id(), ryml::substr{}, /*error_on_excess*/false);
4132  CHECK(output.str == nullptr);
4133  CHECK(output.len > 0);
4134  size_t num_needed_chars = output.len;
4135  std::vector<char> buf(num_needed_chars);
4136  // now try again with the proper buffer
4137  output = ryml::emit_yaml(treea, treea.root_id(), ryml::to_substr(buf), /*error_on_excess*/true);
4138  CHECK(output == ymla);
4139 
4140  // it is possible to reuse the buffer and grow it as needed.
4141  // first do a blank run to find the size:
4142  output = ryml::emit_yaml(treeb, treeb.root_id(), ryml::substr{}, /*error_on_excess*/false);
4143  CHECK(output.str == nullptr);
4144  CHECK(output.len > 0);
4145  CHECK(output.len == ymlb.len);
4146  num_needed_chars = output.len;
4147  buf.resize(num_needed_chars);
4148  // now try again with the proper buffer
4149  output = ryml::emit_yaml(treeb, treeb.root_id(), ryml::to_substr(buf), /*error_on_excess*/true);
4150  CHECK(output == ymlb);
4151 
4152  // there is a convenience wrapper performing the same as above:
4153  // provided to_substr() is defined for that container.
4154  output = ryml::emitrs_yaml(treeb, &buf);
4155  CHECK(output == ymlb);
4156 
4157  // or you can just output a new container:
4158  // provided to_substr() is defined for that container.
4159  std::vector<char> another = ryml::emitrs_yaml<std::vector<char>>(treeb);
4160  CHECK(ryml::to_csubstr(another) == ymlb);
4161 
4162  // you can also emit nested nodes:
4163  another = ryml::emitrs_yaml<std::vector<char>>(treeb[3][2]);
4164  CHECK(ryml::to_csubstr(another) == ""
4165  "more:" "\n"
4166  " vinho verde: Soalheiro" "\n"
4167  " vinho tinto: Redoma 2017" "\n"
4168  "");
4169  }
4170 
4171 
4172  // eg, std::string. notice this is the same code as above
4173  {
4174  // do a blank call on an empty buffer to find the required size.
4175  // no overflow will occur, and returns a substr with the size
4176  // required to output
4177  ryml::csubstr output = ryml::emit_yaml(treea, treea.root_id(), ryml::substr{}, /*error_on_excess*/false);
4178  CHECK(output.str == nullptr);
4179  CHECK(output.len > 0);
4180  size_t num_needed_chars = output.len;
4181  std::string buf;
4182  buf.resize(num_needed_chars);
4183  // now try again with the proper buffer
4184  output = ryml::emit_yaml(treea, treea.root_id(), ryml::to_substr(buf), /*error_on_excess*/true);
4185  CHECK(output == ymla);
4186 
4187  // it is possible to reuse the buffer and grow it as needed.
4188  // first do a blank run to find the size:
4189  output = ryml::emit_yaml(treeb, treeb.root_id(), ryml::substr{}, /*error_on_excess*/false);
4190  CHECK(output.str == nullptr);
4191  CHECK(output.len > 0);
4192  CHECK(output.len == ymlb.len);
4193  num_needed_chars = output.len;
4194  buf.resize(num_needed_chars);
4195  // now try again with the proper buffer
4196  output = ryml::emit_yaml(treeb, treeb.root_id(), ryml::to_substr(buf), /*error_on_excess*/true);
4197  CHECK(output == ymlb);
4198 
4199  // there is a convenience wrapper performing the above instructions:
4200  // provided to_substr() is defined for that container
4201  output = ryml::emitrs_yaml(treeb, &buf);
4202  CHECK(output == ymlb);
4203 
4204  // or you can just output a new container:
4205  // provided to_substr() is defined for that container.
4206  std::string another = ryml::emitrs_yaml<std::string>(treeb);
4207  CHECK(ryml::to_csubstr(another) == ymlb);
4208 
4209  // you can also emit nested nodes:
4210  another = ryml::emitrs_yaml<std::string>(treeb[3][2]);
4211  CHECK(ryml::to_csubstr(another) == ""
4212  "more:" "\n"
4213  " vinho verde: Soalheiro" "\n"
4214  " vinho tinto: Redoma 2017" "\n"
4215  "");
4216  }
4217 }
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:765

◆ sample_emit_to_stream()

void sample_emit_to_stream ( )

demonstrates how to emit to a stream-like structure

Definition at line 4223 of file quickstart.cpp.

4224 {
4225  ryml::csubstr ymlb =
4226  "- a" "\n"
4227  "- b" "\n"
4228  "- x0: 1" "\n"
4229  " x1: 2" "\n"
4230  "- champagne: Dom Perignon" "\n"
4231  " coffee: Arabica" "\n"
4232  " more:" "\n"
4233  " vinho verde: Soalheiro" "\n"
4234  " vinho tinto: Redoma 2017" "\n"
4235  " beer:" "\n"
4236  " - Rochefort 10" "\n"
4237  " - Busch" "\n"
4238  " - Leffe Rituel" "\n"
4239  "- foo" "\n"
4240  "- bar" "\n"
4241  "- baz" "\n"
4242  "- bat" "\n"
4243  "";
4244  const ryml::Tree tree = ryml::parse_in_arena(ymlb);
4245 
4246  std::string s;
4247 
4248  // emit a full tree
4249  {
4250  std::stringstream ss;
4251  ss << tree; // works with any stream having .operator<<() and .write()
4252  s = ss.str();
4253  CHECK(ryml::to_csubstr(s) == ymlb);
4254  }
4255 
4256  // emit a full tree as json
4257  {
4258  std::stringstream ss;
4259  ss << ryml::as_json(tree); // works with any stream having .operator<<() and .write()
4260  s = ss.str();
4261  CHECK(ryml::to_csubstr(s) ==
4262  "[" "\n"
4263  " \"a\"," "\n"
4264  " \"b\"," "\n"
4265  " {" "\n"
4266  " \"x0\": 1," "\n"
4267  " \"x1\": 2" "\n"
4268  " }," "\n"
4269  " {" "\n"
4270  " \"champagne\": \"Dom Perignon\"," "\n"
4271  " \"coffee\": \"Arabica\"," "\n"
4272  " \"more\": {" "\n"
4273  " \"vinho verde\": \"Soalheiro\"," "\n"
4274  " \"vinho tinto\": \"Redoma 2017\"" "\n"
4275  " }," "\n"
4276  " \"beer\": [" "\n"
4277  " \"Rochefort 10\"," "\n"
4278  " \"Busch\"," "\n"
4279  " \"Leffe Rituel\"" "\n"
4280  " ]" "\n"
4281  " }," "\n"
4282  " \"foo\"," "\n"
4283  " \"bar\"," "\n"
4284  " \"baz\"," "\n"
4285  " \"bat\"" "\n"
4286  "]" "\n"
4287  "");
4288  }
4289 
4290  // emit a nested node
4291  {
4292  std::stringstream ss;
4293  ss << tree[3][2]; // works with any stream having .operator<<() and .write()
4294  s = ss.str();
4295  CHECK(ryml::to_csubstr(s) == ""
4296  "more:" "\n"
4297  " vinho verde: Soalheiro" "\n"
4298  " vinho tinto: Redoma 2017" "\n"
4299  "");
4300  }
4301 
4302  // emit a nested node as json
4303  {
4304  std::stringstream ss;
4305  ss << ryml::as_json(tree[3][2]); // works with any stream having .operator<<() and .write()
4306  s = ss.str();
4307  CHECK(ryml::to_csubstr(s) ==
4308  "\"more\": {" "\n"
4309  " \"vinho verde\": \"Soalheiro\"," "\n"
4310  " \"vinho tinto\": \"Redoma 2017\"" "\n"
4311  "}" "\n"
4312  "");
4313  }
4314 }
mark a tree or node to be emitted as yaml when using operator<<, with options.
Definition: emit.hpp:552

◆ sample_emit_to_file()

void sample_emit_to_file ( )

demonstrates how to emit to a FILE*

Definition at line 4320 of file quickstart.cpp.

4321 {
4322  ryml::csubstr yml = ""
4323  "- a" "\n"
4324  "- b" "\n"
4325  "- x0: 1" "\n"
4326  " x1: 2" "\n"
4327  "- champagne: Dom Perignon" "\n"
4328  " coffee: Arabica" "\n"
4329  " more:" "\n"
4330  " vinho verde: Soalheiro" "\n"
4331  " vinho tinto: Redoma 2017" "\n"
4332  " beer:" "\n"
4333  " - Rochefort 10" "\n"
4334  " - Busch" "\n"
4335  " - Leffe Rituel" "\n"
4336  "- foo" "\n"
4337  "- bar" "\n"
4338  "- baz" "\n"
4339  "- bat" "\n"
4340  "";
4341  const ryml::Tree tree = ryml::parse_in_arena(yml);
4342  // this is emitting to stdout, but of course you can pass in any
4343  // FILE* obtained from fopen()
4344  size_t len = ryml::emit_yaml(tree, tree.root_id(), stdout);
4345  // the return value is the number of characters that were written
4346  // to the file
4347  CHECK(len == yml.len);
4348 }

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

4355 {
4356  const ryml::Tree tree = ryml::parse_in_arena(""
4357  "- a" "\n"
4358  "- b" "\n"
4359  "- x0: 1" "\n"
4360  " x1: 2" "\n"
4361  "- champagne: Dom Perignon" "\n"
4362  " coffee: Arabica" "\n"
4363  " more:" "\n"
4364  " vinho verde: Soalheiro" "\n"
4365  " vinho tinto: Redoma 2017" "\n"
4366  " beer:" "\n"
4367  " - Rochefort 10" "\n"
4368  " - Busch" "\n"
4369  " - Leffe Rituel" "\n"
4370  " - - and so" "\n"
4371  " - many other" "\n"
4372  " - wonderful beers" "\n"
4373  "- more" "\n"
4374  "- seq" "\n"
4375  "- members" "\n"
4376  "- here" "\n"
4377  "");
4378  CHECK(ryml::emitrs_yaml<std::string>(tree[3]["beer"]) == ""
4379  "beer:" "\n"
4380  " - Rochefort 10" "\n"
4381  " - Busch" "\n"
4382  " - Leffe Rituel" "\n"
4383  " - - and so" "\n"
4384  " - many other" "\n"
4385  " - wonderful beers" "\n"
4386  "");
4387  CHECK(ryml::emitrs_yaml<std::string>(tree[3]["beer"][0]) == "Rochefort 10");
4388  CHECK(ryml::emitrs_yaml<std::string>(tree[3]["beer"][3]) == ""
4389  "- and so" "\n"
4390  "- many other" "\n"
4391  "- wonderful beers" "\n"
4392  "");
4393 }

◆ sample_style()

void sample_style ( )

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

Definition at line 4400 of file quickstart.cpp.

4401 {
4402  // we will be using this helper throughout this function
4403  auto tostr = [](ryml::ConstNodeRef n) { return ryml::emitrs_yaml<std::string>(n); };
4404  // let's parse this yaml:
4405  ryml::csubstr yaml = ""
4406  "block map:" "\n"
4407  " block key: block val" "\n"
4408  "block seq:" "\n"
4409  " - block val 1" "\n"
4410  " - block val 2" "\n"
4411  " - 'quoted'" "\n"
4412  "flow map, singleline: {flow key: flow val}" "\n"
4413  "flow seq, singleline: [flow val,flow val]" "\n"
4414  "flow map, multiline: {" "\n"
4415  " flow key: flow val" "\n"
4416  " }" "\n"
4417  "flow seq, multiline: [" "\n"
4418  " flow val," "\n"
4419  " flow val" "\n"
4420  " ]" "\n"
4421  "";
4422  ryml::Tree tree = ryml::parse_in_arena(yaml);
4423  // while parsing, ryml marks parsed nodes with their original style:
4424  CHECK(tree.rootref().is_block());
4425  CHECK(tree["block map"].is_key_plain());
4426  CHECK(tree["block seq"].is_key_plain());
4427  CHECK(tree["flow map, singleline"].is_key_plain());
4428  CHECK(tree["flow seq, singleline"].is_key_plain());
4429  CHECK(tree["flow map, multiline"].is_key_plain());
4430  CHECK(tree["flow seq, multiline"].is_key_plain());
4431  CHECK(tree["block map"].is_block());
4432  CHECK(tree["block seq"].is_block());
4433  // flow is either singleline (FLOW_SL) or multiline (FLOW_ML)
4434  CHECK(tree["flow map, singleline"].is_flow_sl());
4435  CHECK(tree["flow seq, singleline"].is_flow_sl());
4436  CHECK(tree["flow map, multiline"].is_flow_ml());
4437  CHECK(tree["flow seq, multiline"].is_flow_ml());
4438  // is_flow() is equivalent to (is_flow_sl() || is_flow_ml())
4439  CHECK(tree["flow map, singleline"].is_flow());
4440  CHECK(tree["flow seq, singleline"].is_flow());
4441  CHECK(tree["flow map, multiline"].is_flow());
4442  CHECK(tree["flow seq, multiline"].is_flow());
4443  //
4444  // since the tree nodes are marked with their original parsed
4445  // style, emitting the parsed tree will preserve the original
4446  // style (minus whitespace):
4447  //
4448  CHECK(tostr(tree) == yaml); // same as before!
4449  //
4450  // you can set/modify the style programatically!
4451  //
4452  // here are more examples.
4453  //
4454  {
4455  ryml::NodeRef n = tree["block map"]; // Let's look at one node
4456  // It looks like this originally:
4457  CHECK(tostr(n) ==
4458  "block map:\n"
4459  " block key: block val\n"
4460  "");
4461  // let's modify its style:
4462  n.set_key_style(ryml::KEY_SQUO); // scalar style: to single-quoted scalar
4463  n.set_container_style(ryml::FLOW_SL); // container style: to flow singleline
4464  // now it looks like this:
4465  CHECK(tostr(n) ==
4466  "'block map': {block key: block val}\n"
4467  "");
4468  }
4469  // next example
4470  {
4471  ryml::NodeRef n = tree["block seq"];
4472  CHECK(tostr(n) == ""
4473  "block seq:\n"
4474  " - block val 1\n"
4475  " - block val 2\n"
4476  " - 'quoted'\n"
4477  "");
4478  n.set_key_style(ryml::KEY_DQUO); // scalar style: to double-quoted scalar
4479  n.set_container_style(ryml::FLOW_ML); // container style: to flow multiline
4480  n[2].set_val_style(ryml::VAL_PLAIN); // scalar style: to plain
4481  CHECK(tostr(n) == ""
4482  "\"block seq\": [\n"
4483  " block val 1,\n"
4484  " block val 2,\n"
4485  " quoted\n"
4486  " ]\n");
4487  }
4488  // next example
4489  {
4490  ryml::NodeRef n = tree["flow map, singleline"];
4491  CHECK(tostr(n) == "flow map, singleline: {flow key: flow val}\n");
4493  n["flow key"].set_val_style(ryml::VAL_LITERAL);
4494  CHECK(tostr(n) == ""
4495  "flow map, singleline:\n"
4496  " flow key: |-\n"
4497  " flow val\n"
4498  "");
4499  }
4500  // next example
4501  {
4502  ryml::NodeRef n = tree["flow map, multiline"];
4503  CHECK(tostr(n) == ""
4504  "flow map, multiline: {\n"
4505  " flow key: flow val\n"
4506  " }\n"
4507  "");
4509  CHECK(tostr(n) == ""
4510  "flow map, multiline:\n"
4511  " flow key: flow val\n"
4512  "");
4513  }
4514  // next example
4515  {
4516  ryml::NodeRef n = tree["flow seq, singleline"];
4517  CHECK(tostr(n) == "flow seq, singleline: [flow val,flow val]\n");
4522  CHECK(tostr(n) == ""
4523  "? >-\n"
4524  " flow seq, singleline\n"
4525  ":\n"
4526  " - 'flow val'\n"
4527  " - \"flow val\"\n"
4528  "");
4529  }
4530  // next example
4531  {
4532  ryml::NodeRef n = tree["flow seq, multiline"];
4533  CHECK(tostr(n) == ""
4534  "flow seq, multiline: [\n"
4535  " flow val,\n"
4536  " flow val\n"
4537  " ]\n"
4538  "");
4540  CHECK(tostr(n) == "flow seq, multiline: [flow val,flow val]\n");
4541  }
4542  // note the full tree now:
4543  CHECK(tostr(tree) != yaml);
4544  CHECK(tostr(tree) ==
4545  "'block map': {block key: block val}" "\n"
4546  "\"block seq\": [" "\n"
4547  " block val 1," "\n"
4548  " block val 2," "\n"
4549  " quoted" "\n"
4550  " ]" "\n"
4551  "flow map, singleline:" "\n"
4552  " flow key: |-" "\n"
4553  " flow val" "\n"
4554  "? >-" "\n"
4555  " flow seq, singleline" "\n"
4556  ":" "\n"
4557  " - 'flow val'" "\n"
4558  " - \"flow val\"" "\n"
4559  "flow map, multiline:" "\n"
4560  " flow key: flow val" "\n"
4561  "flow seq, multiline: [flow val,flow val]" "\n"
4562  "");
4563  // you can clear the style of single nodes:
4564  tree["block map"].clear_style();
4565  tree["block seq"].clear_style();
4566  CHECK(tostr(tree) ==
4567  "block map:" "\n"
4568  " block key: block val" "\n"
4569  "block seq:" "\n"
4570  " - block val 1" "\n"
4571  " - block val 2" "\n"
4572  " - quoted" "\n"
4573  "flow map, singleline:" "\n"
4574  " flow key: |-" "\n"
4575  " flow val" "\n"
4576  "? >-" "\n"
4577  " flow seq, singleline" "\n"
4578  ":" "\n"
4579  " - 'flow val'" "\n"
4580  " - \"flow val\"" "\n"
4581  "flow map, multiline:" "\n"
4582  " flow key: flow val" "\n"
4583  "flow seq, multiline: [flow val,flow val]" "\n"
4584  "");
4585  // you can clear the style recursively:
4586  tree.rootref().clear_style(/*recurse*/true);
4587  // when emitting nodes which have no style set, ryml will default
4588  // to block format for containers, and call
4589  // ryml::scalar_style_choose() to pick the style for each scalar
4590  // (at the cost of a scan over each scalar). Note that ryml picks
4591  // single-quoted for scalars containing commas:
4592  CHECK(tostr(tree) ==
4593  "block map:" "\n"
4594  " block key: block val" "\n"
4595  "block seq:" "\n"
4596  " - block val 1" "\n"
4597  " - block val 2" "\n"
4598  " - quoted" "\n"
4599  "flow map, singleline:" "\n"
4600  " flow key: flow val" "\n"
4601  "flow seq, singleline:" "\n"
4602  " - flow val" "\n"
4603  " - flow val" "\n"
4604  "flow map, multiline:" "\n"
4605  " flow key: flow val" "\n"
4606  "flow seq, multiline:" "\n"
4607  " - flow val" "\n"
4608  " - flow val" "\n"
4609  "");
4610  // you can set the style based on type conditions:
4611  //
4612  // eg, set a single key to single-quoted
4613  tree["block map"].set_style_conditionally(/*type_mask*/ryml::KEY,
4614  /*remflags*/ryml::KEY_STYLE,
4615  /*addflags*/ryml::KEY_SQUO,
4616  /*recurse*/false);
4617  CHECK(tostr(tree) ==
4618  "'block map':" "\n"
4619  " block key: block val" "\n"
4620  "block seq:" "\n"
4621  " - block val 1" "\n"
4622  " - block val 2" "\n"
4623  " - quoted" "\n"
4624  "flow map, singleline:" "\n"
4625  " flow key: flow val" "\n"
4626  "flow seq, singleline:" "\n"
4627  " - flow val" "\n"
4628  " - flow val" "\n"
4629  "flow map, multiline:" "\n"
4630  " flow key: flow val" "\n"
4631  "flow seq, multiline:" "\n"
4632  " - flow val" "\n"
4633  " - flow val" "\n"
4634  "");
4635  // change all keys to single-quoted:
4636  tree.rootref().set_style_conditionally(/*type_mask*/ryml::KEY,
4637  /*remflags*/ryml::KEY_STYLE,
4638  /*addflags*/ryml::KEY_SQUO,
4639  /*recurse*/true);
4640  // change all vals to double-quoted
4641  tree.rootref().set_style_conditionally(/*type_mask*/ryml::VAL,
4642  /*remflags*/ryml::VAL_STYLE,
4643  /*addflags*/ryml::VAL_DQUO,
4644  /*recurse*/true);
4645  // change all seqs to flow
4646  tree.rootref().set_style_conditionally(/*type_mask*/ryml::SEQ,
4647  /*remflags*/ryml::CONTAINER_STYLE,
4648  /*addflags*/ryml::FLOW_SL,
4649  /*recurse*/true);
4650  // change all maps to flow
4651  tree.rootref().set_style_conditionally(/*type_mask*/ryml::MAP,
4652  /*remflags*/ryml::CONTAINER_STYLE,
4653  /*addflags*/ryml::BLOCK,
4654  /*recurse*/true);
4655  // done!
4656  CHECK(tostr(tree) == ""
4657  "'block map':" "\n"
4658  " 'block key': \"block val\"" "\n"
4659  "'block seq': [\"block val 1\",\"block val 2\",\"quoted\"]" "\n"
4660  "'flow map, singleline':" "\n"
4661  " 'flow key': \"flow val\"" "\n"
4662  "'flow seq, singleline': [\"flow val\",\"flow val\"]" "\n"
4663  "'flow map, multiline':" "\n"
4664  " 'flow key': \"flow val\"" "\n"
4665  "'flow seq, multiline': [\"flow val\",\"flow val\"]" "\n"
4666  "");
4667  // you can also set a conditional style in a single node (or its branch if recurse is true):
4668  tree["flow seq, singleline"].set_style_conditionally(/*type_mask*/ryml::SEQ,
4669  /*remflags*/ryml::CONTAINER_STYLE,
4670  /*addflags*/ryml::BLOCK,
4671  /*recurse*/false);
4672  CHECK(tostr(tree) == ""
4673  "'block map':" "\n"
4674  " 'block key': \"block val\"" "\n"
4675  "'block seq': [\"block val 1\",\"block val 2\",\"quoted\"]" "\n"
4676  "'flow map, singleline':" "\n"
4677  " 'flow key': \"flow val\"" "\n"
4678  "'flow seq, singleline':" "\n"
4679  " - \"flow val\"" "\n"
4680  " - \"flow val\"" "\n"
4681  "'flow map, multiline':" "\n"
4682  " 'flow key': \"flow val\"" "\n"
4683  "'flow seq, multiline': [\"flow val\",\"flow val\"]" "\n"
4684  "");
4685  // see also:
4686  // - ryml::scalar_style_choose()
4687  // - ryml::scalar_style_json_choose()
4688  // - ryml::scalar_style_query_squo()
4689  // - ryml::scalar_style_query_plain()
4690 }
void set_key_style(NodeType_e style)
Definition: node.hpp:1145
void set_style_conditionally(NodeType type_mask, NodeType rem_style_flags, NodeType add_style_flags, bool recurse=false)
Definition: node.hpp:1148
void clear_style(bool recurse=false)
Definition: node.hpp:1147
void set_val_style(NodeType_e style)
Definition: node.hpp:1146
void set_container_style(NodeType_e style)
Definition: node.hpp:1144
void clear_style(id_type node, bool recurse=false)
Definition: tree.cpp:1406
void set_style_conditionally(id_type node, NodeType type_mask, NodeType rem_style_flags, NodeType add_style_flags, bool recurse=false)
Definition: tree.cpp:1416
@ KEY_DQUO
mark key scalar as double quoted "
Definition: node_type.hpp:69
@ VAL_STYLE
mask of all the scalar styles for val (not container styles!)
Definition: node_type.hpp:93
@ FLOW_SL
mark container with single-line flow style (seqs as '[val1,val2], maps as '{key: val,...
Definition: node_type.hpp:60
@ FLOW_ML
mark container with multi-line flow style (seqs as '[ val1, val2 ], maps as '{ key: val,...
Definition: node_type.hpp:61
@ VAL
a scalar: has a scalar (ie string) value, possibly empty. must be a leaf node, and cannot be MAP or S...
Definition: node_type.hpp:38
@ VAL_SQUO
mark val scalar as single quoted '
Definition: node_type.hpp:68
@ KEY_STYLE
mask of all the scalar styles for key (not container styles!)
Definition: node_type.hpp:92
@ VAL_PLAIN
mark val scalar as plain scalar (unquoted, even when multiline)
Definition: node_type.hpp:72
@ BLOCK
mark container with block style (seqs as '- val ', maps as 'key: val')
Definition: node_type.hpp:62
@ VAL_DQUO
mark val scalar as double quoted "
Definition: node_type.hpp:70
@ CONTAINER_STYLE
Definition: node_type.hpp:97
@ KEY_SQUO
mark key scalar as single quoted '
Definition: node_type.hpp:67
@ VAL_LITERAL
mark val scalar as multiline, block literal |
Definition: node_type.hpp:64
@ KEY_FOLDED
mark key scalar as multiline, block folded >
Definition: node_type.hpp:65
bool is_block() const RYML_NOEXCEPT
Forward to Tree::is_block().
Definition: node.hpp:280

◆ sample_style_flow_ml_indent()

void sample_style_flow_ml_indent ( )

[experimental] control the indentation of emitted FLOW_ML containers

Definition at line 4696 of file quickstart.cpp.

4697 {
4698  // we will be using this helper throughout this function
4699  auto tostr = [](ryml::ConstNodeRef n, ryml::EmitOptions opts) {
4700  return ryml::emitrs_yaml<std::string>(n, opts);
4701  };
4702  ryml::csubstr yaml = "{map: {seq: [0, 1, 2, 3, [40, 41]]}}";
4703  ryml::Tree tree = ryml::parse_in_arena(yaml);
4704  ryml::EmitOptions defaults = {};
4706  CHECK(tostr(tree, defaults) == "{map: {seq: [0,1,2,3,[40,41]]}}");
4707  // let's now set the style to FLOW_ML (it was FLOW_SL)
4709  tree["map"].set_container_style(ryml::FLOW_ML);
4710  tree["map"]["seq"].set_container_style(ryml::FLOW_ML);
4711  tree["map"]["seq"][4].set_container_style(ryml::FLOW_ML);
4712  // by default FLOW_ML prints one value per line, indented:
4713  CHECK(tostr(tree, defaults) ==
4714  "{" "\n"
4715  " map: {" "\n"
4716  " seq: [" "\n"
4717  " 0," "\n"
4718  " 1," "\n"
4719  " 2," "\n"
4720  " 3," "\n"
4721  " [" "\n"
4722  " 40," "\n"
4723  " 41" "\n"
4724  " ]" "\n"
4725  " ]" "\n"
4726  " }" "\n"
4727  "}" "\n"
4728  "");
4729  // if we use the noindent options, then each value is put at the
4730  // beginning of the line
4731  CHECK(tostr(tree, noindent) ==
4732  "{" "\n"
4733  "map: {" "\n"
4734  "seq: [" "\n"
4735  "0," "\n"
4736  "1," "\n"
4737  "2," "\n"
4738  "3," "\n"
4739  "[" "\n"
4740  "40," "\n"
4741  "41" "\n"
4742  "]" "\n"
4743  "]" "\n"
4744  "}" "\n"
4745  "}" "\n"
4746  "");
4747  // Note that the noindent option will safely respect any prior
4748  // indent level from enclosing block containers! For example:
4750  CHECK(tostr(tree, noindent) == ""// notice it is indented at the map level
4751  "map: {" "\n"
4752  " seq: [" "\n"
4753  " 0," "\n"
4754  " 1," "\n"
4755  " 2," "\n"
4756  " 3," "\n"
4757  " [" "\n"
4758  " 40," "\n"
4759  " 41" "\n"
4760  " ]" "\n"
4761  " ]" "\n"
4762  " }" "\n"
4763  "");
4764  // Let's set it one BLOCK level further:
4765  tree["map"].set_container_style(ryml::BLOCK);
4766  CHECK(tostr(tree, noindent) == // notice it is indented one more level
4767  "map:" "\n"
4768  " seq: [" "\n"
4769  " 0," "\n"
4770  " 1," "\n"
4771  " 2," "\n"
4772  " 3," "\n"
4773  " [" "\n"
4774  " 40," "\n"
4775  " 41" "\n"
4776  " ]" "\n"
4777  " ]" "\n"
4778  "");
4779 }
void set_container_style(id_type node, NodeType_e style)
Definition: tree.hpp:578
bool indent_flow_ml() const noexcept
Definition: emit.hpp:88
A lightweight object containing options to be used when emitting.
Definition: emit.hpp:60

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

4787 {
4788  ryml::csubstr yaml = ""
4789  "{" "\n"
4790  " map: {" "\n"
4791  " seq: [" "\n"
4792  " 0," "\n"
4793  " 1," "\n"
4794  " 2," "\n"
4795  " 3," "\n"
4796  " [" "\n"
4797  " 40," "\n"
4798  " 41" "\n"
4799  " ]" "\n"
4800  " ]" "\n"
4801  " }" "\n"
4802  "}" "\n"
4803  "";
4804  ryml::csubstr yaml_not_indented = ""
4805  "{" "\n"
4806  "map: {" "\n"
4807  "seq: [" "\n"
4808  "0," "\n"
4809  "1," "\n"
4810  "2," "\n"
4811  "3," "\n"
4812  "[" "\n"
4813  "40," "\n"
4814  "41" "\n"
4815  "]" "\n"
4816  "]" "\n"
4817  "}" "\n"
4818  "}" "\n"
4819  "";
4820  // note that the parser defaults to detect multiline flow
4821  // (FLOW_ML) containers:
4822  {
4823  const ryml::Tree tree = ryml::parse_in_arena(yaml);
4824  CHECK(tree["map"].is_flow_ml()); // etc
4825  // emitted yaml is exactly equal to parsed yaml:
4826  CHECK(ryml::emitrs_yaml<std::string>(tree) == yaml);
4827  }
4828  // if you prefer to shorten the emitted yaml, you can set the
4829  // parser to set singleline flow (FLOW_SL) on all flow containers:
4830  {
4832  const ryml::Tree tree = ryml::parse_in_arena(yaml, opts);
4833  CHECK(tree["map"].is_flow_sl()); // etc
4834  // notice how this is smaller now:
4835  CHECK(ryml::emitrs_yaml<std::string>(tree) ==
4836  R"({map: {seq: [0,1,2,3,[40,41]]}})");
4837  }
4838  // you can also keep FLOW_ML, but control its indentation:
4839  // (see more details in @ref sample_style_flow_ml_indent())
4840  {
4841  const ryml::EmitOptions noindent = ryml::EmitOptions{}.indent_flow_ml(false);
4842  const ryml::Tree tree = ryml::parse_in_arena(yaml);
4843  CHECK(tree["map"].is_flow_ml()); // etc
4844  CHECK(ryml::emitrs_yaml<std::string>(tree, noindent) == yaml_not_indented);
4845  }
4846 }
ParserOptions & detect_flow_ml(bool enabled) noexcept
enable/disable detection of FLOW_ML container style.
Definition: common.hpp:386

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

4856 {
4857  ryml::csubstr json = ""
4858  "{" "\n"
4859  " \"doe\": \"a deer, a female deer\"," "\n"
4860  " \"ray\": \"a drop of golden sun\"," "\n"
4861  " \"me\": \"a name, I call myself\"," "\n"
4862  " \"far\": \"a long long way to go\"" "\n"
4863  "}" "\n"
4864  "";
4865  // Since JSON is a subset of YAML, parsing JSON is just the
4866  // same as YAML:
4867  ryml::Tree tree = ryml::parse_in_arena(json);
4868  // If you are sure the source is valid json, you can use the
4869  // appropriate parse_json overload, which is faster because json
4870  // has a smaller grammar:
4871  ryml::Tree json_tree = ryml::parse_json_in_arena(json);
4872  // to emit JSON:
4873  CHECK(ryml::emitrs_json<std::string>(tree) == json);
4874  CHECK(ryml::emitrs_json<std::string>(json_tree) == json);
4875  // to emit JSON to a stream:
4876  std::stringstream ss;
4877  ss << ryml::as_json(tree); // <- mark it like this
4878  CHECK(ss.str() == json);
4879  // Note the following limitations:
4880  //
4881  // - YAML streams cannot be emitted as json, and are not
4882  // allowed. But you can work around this by emitting the
4883  // individual documents separately; see the sample_docs()
4884  // below for such an example.
4885  //
4886  // - tags cannot be emitted as json, and are not allowed.
4887  //
4888  // - anchors and references cannot be emitted as json and
4889  // are not allowed.
4890  //
4891 
4892  // Note that when parsing JSON, ryml will the style of each node
4893  // in the JSON. This means that if you emit as YAML it will look
4894  // mostly the same as the JSON:
4895  std::cout << ryml::emitrs_yaml<std::string>(json_tree);
4896  CHECK(ryml::emitrs_yaml<std::string>(json_tree) == json);
4897  // If you want to avoid this, you will need to clear the style.
4898  json_tree.rootref().clear_style(); // clear the style of the map, but do not recurse
4899  // note that this is now block mode. That is because when no
4900  // style is set, ryml defaults to emitting in block mode.
4901  CHECK(ryml::emitrs_yaml<std::string>(json_tree) == ""
4902  "\"doe\": \"a deer, a female deer\"" "\n"
4903  "\"ray\": \"a drop of golden sun\"" "\n"
4904  "\"me\": \"a name, I call myself\"" "\n"
4905  "\"far\": \"a long long way to go\"" "\n"
4906  "");
4907  // if you don't want the double quotes in the scalar, you can
4908  // recurse:
4909  json_tree.rootref().clear_style(/*recurse*/true);
4910  // so now when emitting you will get this:
4911  // (the scalars with a comma are single-quote)
4912  CHECK(ryml::emitrs_yaml<std::string>(json_tree) == ""
4913  "doe: a deer, a female deer" "\n"
4914  "ray: a drop of golden sun" "\n"
4915  "me: a name, I call myself" "\n"
4916  "far: a long long way to go" "\n"
4917  "");
4918  // you can do custom style changes based on a type mask. this
4919  // will change set the style of all scalar values to single-quoted
4921  /*remflags*/ryml::VAL_STYLE,
4922  /*addflags*/ryml::VAL_SQUO,
4923  /*recurse*/true);
4924  CHECK(ryml::emitrs_yaml<std::string>(json_tree) ==
4925  "doe: 'a deer, a female deer'" "\n"
4926  "ray: 'a drop of golden sun'" "\n"
4927  "me: 'a name, I call myself'" "\n"
4928  "far: 'a long long way to go'" "\n"
4929  "");
4930  // see in particular sample_style() for more examples
4931 }
void parse_json_in_arena(Parser *parser, csubstr filename, csubstr json, Tree *t, id_type node_id)
(1) parse JSON into an existing tree node. The filename will be used in any error messages arising du...
Definition: parse.cpp:113

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

4957 {
4958  std::string unresolved = ""
4959  "base: &base" "\n"
4960  " name: Everyone has same name" "\n"
4961  "foo: &foo" "\n"
4962  " <<: *base" "\n"
4963  " age: 10" "\n"
4964  "bar: &bar" "\n"
4965  " <<: *base" "\n"
4966  " age: 20" "\n"
4967  "bill_to: &id001" "\n"
4968  " street: |-" "\n"
4969  " 123 Tornado Alley" "\n"
4970  " Suite 16" "\n"
4971  " city: East Centerville" "\n"
4972  " state: KS" "\n"
4973  "ship_to: *id001" "\n"
4974  "&keyref key: &valref val" "\n"
4975  "*valref : *keyref" "\n"
4976  "";
4977  std::string resolved = ""
4978  "base:" "\n"
4979  " name: Everyone has same name" "\n"
4980  "foo:" "\n"
4981  " name: Everyone has same name" "\n"
4982  " age: 10" "\n"
4983  "bar:" "\n"
4984  " name: Everyone has same name" "\n"
4985  " age: 20" "\n"
4986  "bill_to:" "\n"
4987  " street: |-" "\n"
4988  " 123 Tornado Alley" "\n"
4989  " Suite 16" "\n"
4990  " city: East Centerville" "\n"
4991  " state: KS" "\n"
4992  "ship_to:" "\n"
4993  " street: |-" "\n"
4994  " 123 Tornado Alley" "\n"
4995  " Suite 16" "\n"
4996  " city: East Centerville" "\n"
4997  " state: KS" "\n"
4998  "key: val" "\n"
4999  "val: key" "\n"
5000  "";
5001 
5002  ryml::Tree tree = ryml::parse_in_arena(ryml::to_csubstr(unresolved));
5003  // by default, references are not resolved when parsing:
5004  CHECK( ! tree["base"].has_key_anchor());
5005  CHECK( tree["base"].has_val_anchor());
5006  CHECK( tree["base"].val_anchor() == "base");
5007  CHECK( tree["key"].key_anchor() == "keyref");
5008  CHECK( tree["key"].val_anchor() == "valref");
5009  CHECK( tree["*valref"].is_key_ref());
5010  CHECK( tree["*valref"].is_val_ref());
5011  CHECK( tree["*valref"].key_ref() == "valref");
5012  CHECK( tree["*valref"].val_ref() == "keyref");
5013 
5014  // to resolve references, simply call tree.resolve(),
5015  // which will perform the reference instantiations:
5016  tree.resolve();
5017 
5018  // all the anchors and references are substistuted and then removed:
5019  CHECK( ! tree["base"].has_key_anchor());
5020  CHECK( ! tree["base"].has_val_anchor());
5021  CHECK( ! tree["base"].has_val_anchor());
5022  CHECK( ! tree["key"].has_key_anchor());
5023  CHECK( ! tree["key"].has_val_anchor());
5024  CHECK( ! tree["val"].is_key_ref()); // notice *valref is now turned to val
5025  CHECK( ! tree["val"].is_val_ref()); // notice *valref is now turned to val
5026 
5027  CHECK(tree["ship_to"]["city"].val() == "East Centerville");
5028  CHECK(tree["ship_to"]["state"].val() == "KS");
5029 }
void resolve(ReferenceResolver *rr, bool clear_anchors=true)
Resolve references (aliases <- anchors), by forwarding to ReferenceResolver::resolve(); refer to Refe...
Definition: tree.cpp:1202

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

5034 {
5035  // part 1: anchor/ref
5036  {
5037  ryml::Tree t;
5038  t.rootref() |= ryml::MAP|ryml::BLOCK;
5039  t["kanchor"] = "2";
5040  t["kanchor"].set_key_anchor("kanchor");
5041  t["vanchor"] = "3";
5042  t["vanchor"].set_val_anchor("vanchor");
5043  // to set a reference, need to call .set_val_ref()/.set_key_ref()
5044  t["kref"].set_val_ref("kanchor");
5045  t["vref"].set_val_ref("vanchor");
5046  t["nref"] = "*vanchor"; // NOTE: this is not set as a reference in the tree!
5047  CHECK(ryml::emitrs_yaml<std::string>(t) == ""
5048  "&kanchor kanchor: 2" "\n"
5049  "vanchor: &vanchor 3" "\n"
5050  "kref: *kanchor" "\n"
5051  "vref: *vanchor" "\n"
5052  // note that ryml emits nref with quotes to disambiguate
5053  // (because no style was set)
5054  "nref: '*vanchor'" "\n"
5055  "");
5056  t.resolve();
5057  CHECK(ryml::emitrs_yaml<std::string>(t) == ""
5058  "kanchor: 2" "\n"
5059  "vanchor: 3" "\n"
5060  "kref: kanchor" "\n"
5061  "vref: 3" "\n"
5062  // note that nref was not resolved
5063  "nref: '*vanchor'" "\n"
5064  "");
5065  }
5066 
5067  // part 2: simple inheritance (ie, adding `<<: *anchor` nodes)
5068  {
5070  "orig: &orig {foo: bar, baz: bat}" "\n"
5071  "copy: {}" "\n"
5072  "notcopy: {}" "\n"
5073  "notref: {}" "\n"
5074  "");
5075  t["copy"]["<<"].set_val_ref("orig");
5076  t["notcopy"]["test"].set_val_ref("orig");
5077  t["notcopy"]["<<"].set_val_ref("orig");
5078  t["notref"]["<<"] = "*orig"; // not a reference! .set_val_ref() was not called
5079  CHECK(ryml::emitrs_yaml<std::string>(t) == ""
5080  "orig: &orig {foo: bar,baz: bat}" "\n"
5081  "copy: {<<: *orig}" "\n"
5082  "notcopy: {test: *orig,<<: *orig}" "\n"
5083  "notref: {<<: '*orig'}" "\n"
5084  "");
5085  t.resolve();
5086  CHECK(ryml::emitrs_yaml<std::string>(t) == ""
5087  "orig: {foo: bar,baz: bat}" "\n"
5088  "copy: {foo: bar,baz: bat}" "\n"
5089  "notcopy: {test: {foo: bar,baz: bat},foo: bar,baz: bat}" "\n"
5090  "notref: {<<: '*orig'}" "\n"
5091  "");
5092  }
5093 
5094  // part 3: multiple inheritance (ie, `<<: [*ref1,*ref2,*etc]`)
5095  {
5097  "orig1: &orig1 {foo: bar}" "\n"
5098  "orig2: &orig2 {baz: bat}" "\n"
5099  "orig3: &orig3 {and: more}" "\n"
5100  "copy: {}" "\n"
5101  "");
5102  ryml::NodeRef seq = t["copy"]["<<"];
5103  seq |= ryml::SEQ;
5104  seq.append_child().set_val_ref("orig1");
5105  seq.append_child().set_val_ref("orig2");
5106  seq.append_child().set_val_ref("orig3");
5107  CHECK(ryml::emitrs_yaml<std::string>(t) == ""
5108  "orig1: &orig1 {foo: bar}" "\n"
5109  "orig2: &orig2 {baz: bat}" "\n"
5110  "orig3: &orig3 {and: more}" "\n"
5111  "copy: {<<: [*orig1,*orig2,*orig3]}" "\n"
5112  "");
5113  t.resolve();
5114  CHECK(ryml::emitrs_yaml<std::string>(t) == ""
5115  "orig1: {foo: bar}" "\n"
5116  "orig2: {baz: bat}" "\n"
5117  "orig3: {and: more}" "\n"
5118  "copy: {foo: bar,baz: bat,and: more}" "\n");
5119  }
5120 }
void set_val_ref(csubstr val_ref)
Definition: node.hpp:1142
void set_val_ref(id_type node, csubstr ref)
Definition: tree.hpp:613
void set_val_anchor(id_type node, csubstr anchor)
Definition: tree.hpp:611
void set_key_anchor(id_type node, csubstr anchor)
Definition: tree.hpp:610

◆ sample_tags()

void sample_tags ( )

Definition at line 5125 of file quickstart.cpp.

5126 {
5127  const std::string yaml = ""
5128  "--- !!map" "\n"
5129  "a: 0" "\n"
5130  "b: 1" "\n"
5131  "--- !map" "\n"
5132  "a: b" "\n"
5133  "--- !!seq" "\n"
5134  "- a" "\n"
5135  "- b" "\n"
5136  "--- !!str a b" "\n"
5137  "--- !!str 'a: b'" "\n"
5138  "---" "\n"
5139  "!!str a: b" "\n"
5140  "--- !!set" "\n"
5141  "? a" "\n"
5142  "? b" "\n"
5143  "--- !!set" "\n"
5144  "a:" "\n"
5145  "--- !!seq" "\n"
5146  "- !!int 0" "\n"
5147  "- !!str 1" "\n"
5148  "";
5149  const ryml::Tree tree = ryml::parse_in_arena(ryml::to_csubstr(yaml));
5150  const ryml::ConstNodeRef root = tree.rootref();
5151  CHECK(root.is_stream());
5152  CHECK(root.num_children() == 9);
5153  for(ryml::ConstNodeRef doc : root.children())
5154  CHECK(doc.is_doc());
5155  // tags are kept verbatim from the source:
5156  CHECK(root[0].has_val_tag());
5157  CHECK(root[0].val_tag() == "!!map"); // valid only if the node has a val tag
5158  CHECK(root[1].val_tag() == "!map");
5159  CHECK(root[2].val_tag() == "!!seq");
5160  CHECK(root[3].val_tag() == "!!str");
5161  CHECK(root[4].val_tag() == "!!str");
5162  CHECK(root[5]["a"].has_key_tag());
5163  CHECK(root[5]["a"].key_tag() == "!!str"); // valid only if the node has a key tag
5164  CHECK(root[6].val_tag() == "!!set");
5165  CHECK(root[7].val_tag() == "!!set");
5166  CHECK(root[8].val_tag() == "!!seq");
5167  CHECK(root[8][0].val_tag() == "!!int");
5168  CHECK(root[8][1].val_tag() == "!!str");
5169  // ryml also provides a complete toolbox to deal with tags.
5170  // there is an enumeration for the standard YAML tags:
5171  CHECK(ryml::to_tag("!map") == ryml::TAG_NONE);
5172  CHECK(ryml::to_tag("!!map") == ryml::TAG_MAP);
5173  CHECK(ryml::to_tag("!!seq") == ryml::TAG_SEQ);
5174  CHECK(ryml::to_tag("!!str") == ryml::TAG_STR);
5175  CHECK(ryml::to_tag("!!int") == ryml::TAG_INT);
5176  CHECK(ryml::to_tag("!!set") == ryml::TAG_SET);
5177  // given a tag enum, you can fetch the short tag string:
5179  CHECK(ryml::from_tag(ryml::TAG_MAP) == "!!map");
5180  CHECK(ryml::from_tag(ryml::TAG_SEQ) == "!!seq");
5181  CHECK(ryml::from_tag(ryml::TAG_STR) == "!!str");
5182  CHECK(ryml::from_tag(ryml::TAG_INT) == "!!int");
5183  CHECK(ryml::from_tag(ryml::TAG_SET) == "!!set");
5184  // you can also fetch the long tag string:
5186  CHECK(ryml::from_tag_long(ryml::TAG_MAP) == "<tag:yaml.org,2002:map>");
5187  CHECK(ryml::from_tag_long(ryml::TAG_SEQ) == "<tag:yaml.org,2002:seq>");
5188  CHECK(ryml::from_tag_long(ryml::TAG_STR) == "<tag:yaml.org,2002:str>");
5189  CHECK(ryml::from_tag_long(ryml::TAG_INT) == "<tag:yaml.org,2002:int>");
5190  CHECK(ryml::from_tag_long(ryml::TAG_SET) == "<tag:yaml.org,2002:set>");
5191  // and likewise:
5192  CHECK(ryml::to_tag("!map") == ryml::TAG_NONE);
5193  CHECK(ryml::to_tag("<tag:yaml.org,2002:map>") == ryml::TAG_MAP);
5194  CHECK(ryml::to_tag("<tag:yaml.org,2002:seq>") == ryml::TAG_SEQ);
5195  CHECK(ryml::to_tag("<tag:yaml.org,2002:str>") == ryml::TAG_STR);
5196  CHECK(ryml::to_tag("<tag:yaml.org,2002:int>") == ryml::TAG_INT);
5197  CHECK(ryml::to_tag("<tag:yaml.org,2002:set>") == ryml::TAG_SET);
5198  // to normalize a tag as much as possible, use normalize_tag():
5199  CHECK(ryml::normalize_tag("!!map") == "!!map");
5200  CHECK(ryml::normalize_tag("!<tag:yaml.org,2002:map>") == "!!map");
5201  CHECK(ryml::normalize_tag("<tag:yaml.org,2002:map>") == "!!map");
5202  CHECK(ryml::normalize_tag("tag:yaml.org,2002:map") == "!!map");
5203  CHECK(ryml::normalize_tag("!<!!map>") == "<!!map>");
5204  CHECK(ryml::normalize_tag("!map") == "!map");
5205  CHECK(ryml::normalize_tag("!my!foo") == "!my!foo");
5206  // and also for the long form:
5207  CHECK(ryml::normalize_tag_long("!!map") == "<tag:yaml.org,2002:map>");
5208  CHECK(ryml::normalize_tag_long("!<tag:yaml.org,2002:map>") == "<tag:yaml.org,2002:map>");
5209  CHECK(ryml::normalize_tag_long("<tag:yaml.org,2002:map>") == "<tag:yaml.org,2002:map>");
5210  CHECK(ryml::normalize_tag_long("tag:yaml.org,2002:map") == "<tag:yaml.org,2002:map>");
5211  CHECK(ryml::normalize_tag_long("!<!!map>") == "<!!map>");
5212  CHECK(ryml::normalize_tag_long("!map") == "!map");
5213  // The tree provides the following methods applying to every node
5214  // with a key and/or val tag:
5215  ryml::Tree normalized_tree = tree;
5216  normalized_tree.normalize_tags(); // normalize all tags in short form
5217  CHECK(ryml::emitrs_yaml<std::string>(normalized_tree) == ""
5218  "--- !!map" "\n"
5219  "a: 0" "\n"
5220  "b: 1" "\n"
5221  "--- !map" "\n"
5222  "a: b" "\n"
5223  "--- !!seq" "\n"
5224  "- a" "\n"
5225  "- b" "\n"
5226  "--- !!str a b" "\n"
5227  "--- !!str 'a: b'" "\n"
5228  "---" "\n"
5229  "!!str a: b" "\n"
5230  "--- !!set" "\n"
5231  "a: " "\n"
5232  "b: " "\n"
5233  "--- !!set" "\n"
5234  "a: " "\n"
5235  "--- !!seq" "\n"
5236  "- !!int 0" "\n"
5237  "- !!str 1" "\n"
5238  "");
5239  ryml::Tree normalized_tree_long = tree;
5240  normalized_tree_long.normalize_tags_long(); // normalize all tags in short form
5241  CHECK(ryml::emitrs_yaml<std::string>(normalized_tree_long) == ""
5242  "--- !<tag:yaml.org,2002:map>" "\n"
5243  "a: 0" "\n"
5244  "b: 1" "\n"
5245  "--- !map" "\n"
5246  "a: b" "\n"
5247  "--- !<tag:yaml.org,2002:seq>" "\n"
5248  "- a" "\n"
5249  "- b" "\n"
5250  "--- !<tag:yaml.org,2002:str> a b" "\n"
5251  "--- !<tag:yaml.org,2002:str> 'a: b'" "\n"
5252  "---" "\n"
5253  "!<tag:yaml.org,2002:str> a: b" "\n"
5254  "--- !<tag:yaml.org,2002:set>" "\n"
5255  "a: " "\n"
5256  "b: " "\n"
5257  "--- !<tag:yaml.org,2002:set>" "\n"
5258  "a: " "\n"
5259  "--- !<tag:yaml.org,2002:seq>" "\n"
5260  "- !<tag:yaml.org,2002:int> 0" "\n"
5261  "- !<tag:yaml.org,2002:str> 1" "\n"
5262  "");
5263 }
void normalize_tags()
Definition: tree.cpp:1573
void normalize_tags_long()
Definition: tree.cpp:1580
csubstr from_tag_long(YamlTag_e tag)
Definition: tag.cpp:130
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:170
YamlTag_e to_tag(csubstr tag)
Definition: tag.cpp:68
@ TAG_SET
!!set Unordered set of non-equal values.
Definition: tag.hpp:39
@ TAG_INT
!!float Mathematical integers.
Definition: tag.hpp:45
@ TAG_SEQ
!!seq Sequence of arbitrary values.
Definition: tag.hpp:40
@ TAG_NONE
Definition: tag.hpp:34
@ TAG_STR
!!str A sequence of zero or more Unicode characters.
Definition: tag.hpp:48
@ TAG_MAP
!!map Unordered set of key: value pairs without duplicates.
Definition: tag.hpp:36
bool is_stream() const RYML_NOEXCEPT
Forward to Tree::is_stream().
Definition: node.hpp:238

◆ sample_tag_directives()

void sample_tag_directives ( )

Definition at line 5268 of file quickstart.cpp.

5269 {
5270  const std::string yaml = ""
5271  "%TAG !m! !my-" "\n"
5272  "--- # Bulb here" "\n"
5273  "!m!light fluorescent" "\n"
5274  "..." "\n"
5275  "%TAG !m! !meta-" "\n"
5276  "--- # Color here" "\n"
5277  "!m!light green" "\n"
5278  "";
5279  // tags are not resolved by default:
5281  CHECK(ryml::emitrs_yaml<std::string>(tree) == ""
5282  "%TAG !m! !my-" "\n"
5283  "--- !m!light fluorescent" "\n"
5284  "..." "\n"
5285  "%TAG !m! !meta-" "\n"
5286  "--- !m!light green" "\n"
5287  "");
5288  // Use Tree::resolve_tags() to accomplish this in an existing
5289  // tree:
5290  ryml::TagCache tag_cache; // reduces memory requirements by reusing resolved tags
5291  tree.resolve_tags(tag_cache);
5292  CHECK(ryml::emitrs_yaml<std::string>(tree) == ""
5293  "%TAG !m! !my-" "\n"
5294  "--- !<!my-light> fluorescent" "\n"
5295  "..." "\n"
5296  "%TAG !m! !meta-" "\n"
5297  "--- !<!meta-light> green" "\n"
5298  "");
5299  // You can also Use ParserOptions to force resolution of tags
5300  // while parsing:
5302  ryml::Tree resolved_tree = ryml::parse_in_arena(ryml::to_csubstr(yaml), opts);
5303  CHECK(ryml::emitrs_yaml<std::string>(resolved_tree) == ""
5304  "%TAG !m! !my-" "\n"
5305  "--- !<!my-light> fluorescent" "\n"
5306  "..." "\n"
5307  "%TAG !m! !meta-" "\n"
5308  "--- !<!meta-light> green" "\n"
5309  "");
5310  // see also tree.normalize_tags()
5311  // see also tree.normalize_tags_long()
5312 }
void resolve_tags(TagCache &cache, bool all=true)
Resolve tags in the tree such as "!!str" -> "<tag:yaml.org,2002:str>", "!foo" -> "<!...
Definition: tree.cpp:1555
ParserOptions & resolve_tags(bool enabled) noexcept
enable/disable resolution of YAML tags during parsing.
Definition: common.hpp:404
Accelerator structure to reduce memory requirements by enabling reuse of resolved tags.
Definition: tag.hpp:71

◆ sample_docs()

void sample_docs ( )

Definition at line 5317 of file quickstart.cpp.

5318 {
5319  std::string yml = ""
5320  "---" "\n"
5321  "a: 0" "\n"
5322  "b: 1" "\n"
5323  "---" "\n"
5324  "c: 2" "\n"
5325  "d: 3" "\n"
5326  "---" "\n"
5327  "- 4" "\n"
5328  "- 5" "\n"
5329  "- 6" "\n"
5330  "- 7" "\n"
5331  "";
5333  CHECK(ryml::emitrs_yaml<std::string>(tree) == yml);
5334 
5335  // iteration through docs
5336  {
5337  // using the node API
5338  const ryml::ConstNodeRef stream = tree.rootref();
5339  CHECK(stream.is_root());
5340  CHECK(stream.is_stream());
5341  CHECK(!stream.is_doc());
5342  CHECK(stream.num_children() == 3);
5343  for(const ryml::ConstNodeRef doc : stream.children())
5344  CHECK(doc.is_doc());
5345  CHECK(tree.docref(0).id() == stream.child(0).id());
5346  CHECK(tree.docref(1).id() == stream.child(1).id());
5347  CHECK(tree.docref(2).id() == stream.child(2).id());
5348  // equivalent: using the lower level index API
5349  const ryml::id_type stream_id = tree.root_id();
5350  CHECK(tree.is_root(stream_id));
5351  CHECK(tree.is_stream(stream_id));
5352  CHECK(!tree.is_doc(stream_id));
5353  CHECK(tree.num_children(stream_id) == 3);
5354  for(ryml::id_type doc_id = tree.first_child(stream_id);
5355  doc_id != ryml::NONE;
5356  doc_id = tree.next_sibling(stream_id))
5357  CHECK(tree.is_doc(doc_id));
5358  CHECK(tree.doc(0) == tree.child(stream_id, 0));
5359  CHECK(tree.doc(1) == tree.child(stream_id, 1));
5360  CHECK(tree.doc(2) == tree.child(stream_id, 2));
5361 
5362  // using the node API
5363  CHECK(stream[0].is_doc());
5364  CHECK(stream[0].is_map());
5365  CHECK(stream[0]["a"].val() == "0");
5366  CHECK(stream[0]["b"].val() == "1");
5367  // equivalent: using the index API
5368  const ryml::id_type doc0_id = tree.first_child(stream_id);
5369  CHECK(tree.is_doc(doc0_id));
5370  CHECK(tree.is_map(doc0_id));
5371  CHECK(tree.val(tree.find_child(doc0_id, "a")) == "0");
5372  CHECK(tree.val(tree.find_child(doc0_id, "b")) == "1");
5373 
5374  // using the node API
5375  CHECK(stream[1].is_doc());
5376  CHECK(stream[1].is_map());
5377  CHECK(stream[1]["c"].val() == "2");
5378  CHECK(stream[1]["d"].val() == "3");
5379  // equivalent: using the index API
5380  const ryml::id_type doc1_id = tree.next_sibling(doc0_id);
5381  CHECK(tree.is_doc(doc1_id));
5382  CHECK(tree.is_map(doc1_id));
5383  CHECK(tree.val(tree.find_child(doc1_id, "c")) == "2");
5384  CHECK(tree.val(tree.find_child(doc1_id, "d")) == "3");
5385 
5386  // using the node API
5387  CHECK(stream[2].is_doc());
5388  CHECK(stream[2].is_seq());
5389  CHECK(stream[2][0].val() == "4");
5390  CHECK(stream[2][1].val() == "5");
5391  CHECK(stream[2][2].val() == "6");
5392  CHECK(stream[2][3].val() == "7");
5393  // equivalent: using the index API
5394  const ryml::id_type doc2_id = tree.next_sibling(doc1_id);
5395  CHECK(tree.is_doc(doc2_id));
5396  CHECK(tree.is_seq(doc2_id));
5397  CHECK(tree.val(tree.child(doc2_id, 0)) == "4");
5398  CHECK(tree.val(tree.child(doc2_id, 1)) == "5");
5399  CHECK(tree.val(tree.child(doc2_id, 2)) == "6");
5400  CHECK(tree.val(tree.child(doc2_id, 3)) == "7");
5401  }
5402 
5403  // Note: since json does not have streams, you cannot emit the above
5404  // tree as json when you start from the root:
5405  //CHECK(ryml::emitrs_json<std::string>(tree) == yml); // RUNTIME ERROR!
5406 
5407  // but, althouth emitting streams as json is not possible,
5408  // you can iterate through individual documents and emit
5409  // them separately:
5410  {
5411  const std::string expected_json[] = {
5412  "{" "\n"
5413  " \"a\": 0," "\n"
5414  " \"b\": 1" "\n"
5415  "}" "\n"
5416  "",
5417  "{" "\n"
5418  " \"c\": 2," "\n"
5419  " \"d\": 3" "\n"
5420  "}" "\n"
5421  "",
5422  "[" "\n"
5423  " 4," "\n"
5424  " 5," "\n"
5425  " 6," "\n"
5426  " 7" "\n"
5427  "]" "\n"
5428  "",
5429  };
5430  // using the node API
5431  {
5432  ryml::id_type count = 0;
5433  const ryml::ConstNodeRef stream = tree.rootref();
5434  CHECK(stream.num_children() == (ryml::id_type)C4_COUNTOF(expected_json));
5435  for(ryml::ConstNodeRef doc : stream.children())
5436  {
5437  CHECK(ryml::emitrs_json<std::string>(doc) == expected_json[count++]);
5438  }
5439  }
5440  // equivalent: using the index API
5441  {
5442  ryml::id_type count = 0;
5443  const ryml::id_type stream_id = tree.root_id();
5444  CHECK(tree.num_children(stream_id) == (ryml::id_type)C4_COUNTOF(expected_json));
5445  for(ryml::id_type doc_id = tree.first_child(stream_id);
5446  doc_id != ryml::NONE;
5447  doc_id = tree.next_sibling(doc_id))
5448  {
5449  CHECK(ryml::emitrs_json<std::string>(tree, doc_id) == expected_json[count++]);
5450  }
5451  }
5452  }
5453 }
id_type id() const noexcept
Definition: node.hpp:1121
bool is_stream(id_type node) const
Definition: tree.hpp:408
bool is_root(id_type node) const
Definition: tree.hpp:460
NodeRef docref(id_type i)
get the i-th document of the stream
Definition: tree.cpp:104
bool is_doc(id_type node) const
Definition: tree.hpp:409
id_type doc(id_type i) const
gets the i document node index.
Definition: tree.hpp:530
id_type num_children(id_type node) const
O(num_children)
Definition: tree.cpp:1212
id_type child(id_type node, id_type pos) const
Definition: tree.cpp:1220
bool is_root() const RYML_NOEXCEPT
Forward to Tree::is_root().
Definition: node.hpp:310
bool is_doc() const RYML_NOEXCEPT
Forward to Tree::is_doc().
Definition: node.hpp:239
auto child(id_type pos) RYML_NOEXCEPT -> Impl
Forward to Tree::child().
Definition: node.hpp:352

◆ sample_error_handler()

void sample_error_handler ( )

demonstrates how to set a custom error handler for ryml

Definition at line 5476 of file quickstart.cpp.

5477 {
5478  ErrorHandlerExample errh; // browse the implementation of this
5479  // class to understand more details
5480  errh.check_disabled();
5481  // set the global error handlers. Note the error callbacks must
5482  // never return: they must either throw an exception, use setjmp()
5483  // and longjmp(), or abort. Otherwise, the parser will enter into
5484  // an infinite loop, or the program may crash.
5486  errh.check_enabled();
5487  CHECK(errh.check_error_occurs([&]{
5488  ryml::Tree tree = ryml::parse_in_arena("errorhandler.yml", "[a: b\n}");
5489  }));
5490  ryml::set_callbacks(errh.original_callbacks); // restore defaults.
5491  errh.check_disabled();
5492 }
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 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:241
ryml::Callbacks original_callbacks
Definition: quickstart.cpp:243

◆ sample_error_basic()

void sample_error_basic ( )

Definition at line 5497 of file quickstart.cpp.

5498 {
5499  auto cause_basic_error = []{
5500  ryml::Tree t;
5501  ryml::csubstr tag_handle = {}, tag_prefix = {}; // invalid, not filled
5502  t.add_tag_directive(tag_handle, tag_prefix, 0);
5503  };
5504  {
5505  ScopedErrorHandlerExample errh; // set the example callbacks (scoped)
5506  CHECK(errh.check_error_occurs(cause_basic_error));
5507  }
5508 #ifdef _RYML_WITH_EXCEPTIONS
5509  bool gotit = false;
5510  try
5511  {
5512  cause_basic_error();
5513  }
5514  catch(ryml::ExceptionBasic const& exc)
5515  {
5516  gotit = true;
5517  ryml::csubstr msg = ryml::to_csubstr(exc.what());
5518  CHECK(!exc.errdata_basic.location.name.empty());
5519  CHECK(!msg.empty());
5520  }
5521  CHECK(gotit);
5522 #endif
5523 }
void add_tag_directive(csubstr handle, csubstr prefix, id_type id)
Definition: tree.cpp:1446
Location location
location where the error was detected (may be from YAML or C++ source code)
Definition: common.hpp:316
Exception thrown by the default basic error implementation.
Definition: error.hpp:478
ErrorDataBasic errdata_basic
error data
Definition: error.hpp:481
const char * what() const noexcept override
Definition: error.hpp:480
csubstr name
name of the file
Definition: common.hpp:288

◆ sample_error_parse()

void sample_error_parse ( )

Definition at line 5526 of file quickstart.cpp.

5527 {
5528  ryml::csubstr ymlsrc = ""
5529  "{" "\n"
5530  " a: b" "\n"
5531  " [" "\n"
5532  "";
5533  ryml::csubstr ymlfile = "file.yml";
5534  auto cause_parse_error = [&]{
5535  return ryml::parse_in_arena(ymlfile, ymlsrc);
5536  };
5537  // the YAML in ymlsrc must cause a parse error while it is being
5538  // parsed. We use our error handler to catch that error, and save
5539  // the error info:
5540  ErrorHandlerExample errh;
5541  {
5543  CHECK(errh.check_error_occurs(cause_parse_error));
5544  // the handler in errh saves the error info in itself. Let's
5545  // use that to see the messages we get.
5546  //
5547  // this message is the short message passed into the parse
5548  // error handler:
5549  CHECK(errh.saved_msg_short == "invalid character: '['");
5550  // this message was created inside the handler, by calling
5551  // ryml::err_parse_format():
5552  CHECK(ryml::to_csubstr(errh.saved_msg_full).begins_with("file.yml:3: col=4 (12B): ERROR: [parse] invalid character: '['"));
5553  // If you keep the YAML source buffer around, you can also use
5554  // it to create/print a larger error message showing the
5555  // YAML source code context which causes the error:
5556  std::string msg_ctx = errh.saved_msg_full + "\n";
5557  ryml::location_format_with_context([&msg_ctx](ryml::csubstr s){
5558  msg_ctx.append(s.str, s.len);
5559  }, errh.saved_parse_loc, ymlsrc, "err");
5560  CHECK(ryml::to_csubstr(msg_ctx).begins_with("file.yml:3: col=4 (12B): ERROR: [parse] invalid character: '['"));
5561  CHECK(ryml::to_csubstr(msg_ctx).ends_with(
5562  "file.yml:3: col=4 (12B): err:" "\n"
5563  "err:" "\n"
5564  "err: [" "\n"
5565  "err: |" "\n"
5566  "err: (here)" "\n"
5567  "err:" "\n"
5568  "err: see region:" "\n"
5569  "err:" "\n"
5570  "err: {" "\n"
5571  "err: a: b" "\n"
5572  "err: [" "\n"
5573  "err: |" "\n"
5574  "err: (here)" "\n"
5575  ""));
5576  //
5577  // Let's now check the location (see the message above):
5578  CHECK(errh.saved_parse_loc.name == ymlfile);
5579  CHECK(errh.saved_parse_loc.line == 3);
5580  CHECK(errh.saved_parse_loc.col == 4);
5581  CHECK(errh.saved_parse_loc.offset == 12);
5582  CHECK(errh.saved_parse_loc.offset <= ymlsrc.len);
5583  // ... and this is the location in the ryml source code file where
5584  // this error was found:
5585  CHECK(!errh.saved_basic_loc.name.empty());
5586  CHECK(errh.saved_basic_loc.line > 0);
5587  CHECK(errh.saved_basic_loc.col > 0);
5588  CHECK(errh.saved_basic_loc.offset > 0);
5590  }
5591  // A parse error is also a basic error. If no parse error handler
5592  // is set, then ryml falls back to a basic error:
5593  {
5594  ryml::Callbacks cb = errh.callbacks();
5595  cb.m_error_parse = nullptr;
5596  ryml::set_callbacks(cb);
5597  CHECK(ryml::get_callbacks().m_error_parse == nullptr);
5598  CHECK(errh.check_error_occurs(cause_parse_error));
5599  // we got a basic error instead of a parse error:
5600  CHECK(errh.saved_msg_short == "invalid character: '['");
5601  // notice that the full message now displays this as a basic
5602  // error:
5603  CHECK(errh.saved_msg_full == "file.yml:3: col=4 (12B): ERROR: [basic] invalid character: '['");
5604  // the yml location is now in the location saved from the basic error
5605  CHECK(errh.saved_basic_loc.name == ymlfile);
5606  CHECK(errh.saved_basic_loc.line == 3);
5607  CHECK(errh.saved_basic_loc.col == 4);
5608  CHECK(errh.saved_basic_loc.offset == 12);
5609  CHECK(errh.saved_basic_loc.offset <= ymlsrc.len);
5610  CHECK(errh.saved_parse_loc.name == ryml::csubstr{});
5615  }
5616 #ifdef _RYML_WITH_EXCEPTIONS
5617  bool gotit = false;
5618  try
5619  {
5620  cause_parse_error();
5621  }
5622  catch(ryml::ExceptionParse const& exc)
5623  {
5624  gotit = true;
5625  ryml::csubstr msg = ryml::to_csubstr(exc.what());
5626  CHECK(exc.errdata_parse.ymlloc.name == ymlfile);
5627  CHECK(exc.errdata_parse.ymlloc.line == 3);
5628  CHECK(exc.errdata_parse.ymlloc.col == 4);
5629  CHECK(exc.errdata_parse.ymlloc.offset == 12);
5630  CHECK(exc.errdata_parse.ymlloc.offset <= ymlsrc.len);
5631  // the message saved in the exception is just the concrete error description:
5632  CHECK(msg == "invalid character: '['");
5633  // to print richer error messages, ryml provides helpers to
5634  // format that description into a complete error message,
5635  // containing location and source context indication:
5636  std::string full;
5637  auto dumpfn = [&full](ryml::csubstr s) { full.append(s.str, s.len); };
5638  ryml::err_parse_format(dumpfn, msg, exc.errdata_parse);
5639  full += '\n';
5640  ryml::location_format_with_context(dumpfn, exc.errdata_parse.ymlloc, ymlsrc, "err", 3);
5641  CHECK(ryml::to_csubstr(full).begins_with("file.yml:3: col=4 (12B): ERROR: [parse] invalid character: '['"));
5642  CHECK(ryml::to_csubstr(full).ends_with(
5643  "file.yml:3: col=4 (12B): err:" "\n"
5644  "err:" "\n"
5645  "err: [" "\n"
5646  "err: |" "\n"
5647  "err: (here)" "\n"
5648  "err:" "\n"
5649  "err: see region:" "\n"
5650  "err:" "\n"
5651  "err: {" "\n"
5652  "err: a: b" "\n"
5653  "err: [" "\n"
5654  "err: |" "\n"
5655  "err: (here)" "\n"
5656  ""));
5657  }
5658  CHECK(gotit);
5659  gotit = false;
5660  try
5661  {
5662  cause_parse_error();
5663  }
5664  catch(ryml::ExceptionBasic const& exc) // use references! don't slice the exception
5665  {
5666  gotit = true;
5667  ryml::csubstr msg = ryml::to_csubstr(exc.what());
5668  CHECK(!exc.errdata_basic.location.name.empty());
5669  CHECK(!msg.empty());
5670  }
5671  CHECK(gotit);
5672 #endif
5673 }
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:86
void err_parse_format(DumpFn &&dumpfn, csubstr msg, ErrorDataParse const &errdata)
Given an error message and associated parse error data, format it fully as a parse error message.
Definition: error.def.hpp:331
ryml::Location saved_basic_loc
Definition: quickstart.cpp:268
std::string saved_msg_full
Definition: quickstart.cpp:266
std::string saved_msg_short
Definition: quickstart.cpp:265
ryml::Location saved_parse_loc
Definition: quickstart.cpp:269
A c-style callbacks class to customize behavior on errors or allocation.
Definition: common.hpp:541
pfn_error_parse m_error_parse
a pointer to a parse error handler function
Definition: common.hpp:546
Location ymlloc
location in the YAML source buffer where the error was detected.
Definition: common.hpp:326
Exception thrown by the default parse error implementation.
Definition: error.hpp:497
ErrorDataParse errdata_parse
Definition: error.hpp:499
size_t offset
number of bytes from the beginning of the source buffer
Definition: common.hpp:285

◆ sample_error_visit()

void sample_error_visit ( )

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

Definition at line 5678 of file quickstart.cpp.

5679 {
5680  ryml::csubstr ymlfile = "file.yml";
5681  ryml::csubstr ymlsrc = "float: 123.456";
5682  ErrorHandlerExample errh;
5683  {
5685  ryml::Tree tree = ryml::parse_in_arena(ymlfile, ymlsrc);
5686  CHECK(errh.check_error_occurs([&]{
5687  int intval = 0;
5688  tree["float"] >> intval; // cannot deserialize 123.456 to int
5689  }));
5690  // the handler in errh saves the error info in itself. Let's
5691  // use that to see the messages we get.
5692  //
5693  // this message is the short message passed into the visit error
5694  CHECK(errh.saved_msg_short == "could not deserialize value");
5695  // this message was created inside the handler, by calling
5696  // ryml::err_visit_format():
5697  CHECK(ryml::csubstr::npos != ryml::to_csubstr(errh.saved_msg_full).find("ERROR: [visit] could not deserialize value"));
5698  // The location of the visit error is of the C++ source file where
5699  // the error was detected -- NOT of the YAML source file:
5700  CHECK(errh.saved_basic_loc.name != ymlfile);
5701  // However, note that the tree and node id are available:
5702  CHECK(errh.saved_visit_tree == &tree);
5703  CHECK(errh.saved_visit_id == tree["float"].id());
5704  // see sample_error_visit_location() for an example on how
5705  // to extract the location.
5706  }
5707  // visit errors also fall back to basic errors when the visit
5708  // handler is not set (similar to the behavior of ExceptionVisit):
5709  {
5710  ryml::Callbacks cb = errh.callbacks();
5711  cb.m_error_visit = nullptr;
5712  ryml::set_callbacks(cb);
5713  CHECK(ryml::get_callbacks().m_error_visit == nullptr);
5714  ryml::Tree tree = ryml::parse_in_arena(ymlfile, ymlsrc);
5715  CHECK(errh.check_error_occurs([&]{
5716  int intval = 0;
5717  tree["float"] >> intval; // cannot deserialize 123.456 to int
5718  }));
5719  // we got a basic error instead of a visit error:
5720  CHECK(errh.saved_msg_short == "could not deserialize value");
5721  // notice that the full message now displays this as a basic
5722  // error:
5723  CHECK(ryml::csubstr::npos != ryml::to_csubstr(errh.saved_msg_full).find("ERROR: [basic] could not deserialize value"));
5724  // the tree and id are not set, because this was called as a basic error
5725  CHECK(errh.saved_visit_tree == nullptr);
5726  CHECK(errh.saved_visit_id == ryml::NONE);
5728  }
5729 #ifdef _RYML_WITH_EXCEPTIONS
5730  // when using the default ryml callbacks (see
5731  // RYML_NO_DEFAULT_CALLBACKS), and
5732  // RYML_DEFAULT_CALLBACK_USES_EXCEPTIONS is defined, the ryml
5733  // parse handler throws an exception of type ryml::ExceptionVisit,
5734  // which is derived from ryml::ExceptionBasic.
5735  {
5736  const ryml::Tree tree = ryml::parse_in_arena(ymlfile, ymlsrc);
5737  bool gotit = false;
5738  try
5739  {
5740  int intval = 0;
5741  tree["float"] >> intval; // cannot deserialize 123.456 to int
5742  }
5743  catch(ryml::ExceptionVisit const& exc)
5744  {
5745  gotit = true;
5746  ryml::csubstr msg = ryml::to_csubstr(exc.what());
5747  CHECK(!exc.errdata_visit.cpploc.name.empty());
5748  CHECK(exc.errdata_visit.tree == &tree);
5749  CHECK(exc.errdata_visit.node == tree["float"].id());
5750  CHECK(!msg.empty());
5751  }
5752  CHECK(gotit);
5753  }
5754  // you can also catch the exception as its base,
5755  // ryml::ExceptionBasic:
5756  {
5757  const ryml::Tree tree = ryml::parse_in_arena(ymlfile, ymlsrc);
5758  bool gotit = false;
5759  try
5760  {
5761  int intval = 0;
5762  tree["float"] >> intval; // cannot deserialize 123.456 to int
5763  }
5764  catch(ryml::ExceptionBasic const& exc) // use references! don't slice the exception
5765  {
5766  gotit = true;
5767  ryml::csubstr msg = ryml::to_csubstr(exc.what());
5768  CHECK(!exc.errdata_basic.location.name.empty());
5769  CHECK(!msg.empty());
5770  }
5771  CHECK(gotit);
5772  }
5773 #endif
5774 }
ryml::id_type saved_visit_id
Definition: quickstart.cpp:271
ryml::Tree const * saved_visit_tree
Definition: quickstart.cpp:270
pfn_error_visit m_error_visit
a pointer to a visit error handler function
Definition: common.hpp:547
Location cpploc
location in the C++ source file where the error was detected.
Definition: common.hpp:335
Tree const * tree
tree where the error was detected
Definition: common.hpp:336
id_type node
node where the error was detected
Definition: common.hpp:337
Exception thrown by the default visit error implementation.
Definition: error.hpp:514
ErrorDataVisit errdata_visit
Definition: error.hpp:516

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

5783 {
5785  // we will use locations to show the YAML source context of the
5786  // node where the visit error was triggered. This is a very
5787  // convenient feature to show detailed messages when deserializing
5788  // data read from a file (but do note this is opt-in, and it is
5789  // not mandatory). See sample_location_tracking() for more details
5790  // on location tracking.
5792  ryml::EventHandlerTree evt_handler{};
5793  ryml::Parser parser(&evt_handler, opts);
5794  ryml::csubstr ymlfile = "file.yml";
5795  ryml::csubstr ymlsrc = ""
5796  "foo: bar" "\n"
5797  "char: a" "\n"
5798  "int: a" "\n"
5799  "float: 123.456" "\n"
5800  "";
5801  const ryml::Tree tree = ryml::parse_in_arena(&parser, ymlfile, ymlsrc);
5802  // This function will cause a visit error when being called:
5803  auto cause_visit_error = [&]{
5804  int intval = 0;
5805  tree["float"] >> intval; // cannot deserialize 123.456 to int
5806  };
5807  // Like with the parse error, we will use our error handler to
5808  // catch that visit error, and save the error info:
5809  CHECK(evt_handler.callbacks() == errh.callbacks());
5810  CHECK(parser.callbacks() == errh.callbacks());
5811  CHECK(tree.callbacks() == errh.callbacks());
5812  {
5813  CHECK(errh.check_error_occurs(cause_visit_error));
5814  // the handler in errh saves the error info in itself. Let's
5815  // use that to see the messages we get.
5816  //
5817  // this message is the short message passed into the visit error
5818  CHECK(errh.saved_msg_short == "could not deserialize value");
5819  // this message was created inside the handler, by calling
5820  // ryml::err_visit_format():
5821  CHECK(ryml::csubstr::npos != ryml::to_csubstr(errh.saved_msg_full).find("ERROR: [visit] could not deserialize value"));
5822  // The location of the visit error is of the C++ source file where
5823  // the error was detected -- NOT of the YAML source file:
5824  CHECK(errh.saved_basic_loc.name != ymlfile);
5825  // However, note that the tree and node id are available:
5826  CHECK(errh.saved_visit_tree == &tree);
5827  CHECK(errh.saved_visit_id == tree["float"].id());
5828  // ... which we can use to get the location in the YAML source
5829  // from the parser (but see @ref sample_location_tracking()):
5830  ryml::Location ymlloc = errh.saved_visit_tree->location(parser, errh.saved_visit_id);
5831  CHECK(ymlloc.name == ymlfile);
5832  // In turn, we can use format_location_context() to
5833  // print/create an error message pointing at the YAML source
5834  // code:
5835  std::string msg = errh.saved_msg_full;
5836  ryml::location_format_with_context([&msg](ryml::csubstr s){
5837  msg.append(s.str, s.len);
5838  }, ymlloc, ymlsrc, "err", /*number of lines to show before the error*/3);
5839  CHECK(ryml::to_csubstr(msg).ends_with(
5840  "file.yml:3: col=0 (24B): err:" "\n"
5841  "err:" "\n"
5842  "err: float: 123.456" "\n"
5843  "err: |" "\n"
5844  "err: (here)" "\n"
5845  "err:" "\n"
5846  "err: see region:" "\n"
5847  "err:" "\n"
5848  "err: foo: bar" "\n"
5849  "err: char: a" "\n"
5850  "err: int: a" "\n"
5851  "err: float: 123.456" "\n"
5852  "err: |" "\n"
5853  "err: (here)" "\n"
5854  ""));
5855  }
5856 }
Location location(Parser const &p, id_type node) const
Get the location of a node from the parse used to parse this tree.
Definition: tree.cpp:1914
ParserOptions & locations(bool enabled) noexcept
enable/disable source location tracking
Definition: common.hpp:429

◆ sample_global_allocator()

void sample_global_allocator ( )

demonstrates how to set the global allocator for ryml

Definition at line 5956 of file quickstart.cpp.

5957 {
5959 
5960  // save the existing callbacks for restoring
5961  ryml::Callbacks defaults = ryml::get_callbacks();
5962 
5963  // set to our callbacks
5965 
5966  // verify that the allocator is in effect
5967  ryml::Callbacks const& current = ryml::get_callbacks();
5968  CHECK(current.m_allocate == &mem.s_allocate);
5969  CHECK(current.m_free == &mem.s_free);
5970 
5971  // so far nothing was allocated
5972  CHECK(mem.alloc_size == 0);
5973 
5974  // parse one tree and check
5975  (void)ryml::parse_in_arena(R"({foo: bar})");
5976  mem.check_and_reset();
5977 
5978  // parse another tree and check
5979  (void)ryml::parse_in_arena(R"([a, b, c, d, {foo: bar, money: pennys}])");
5980  mem.check_and_reset();
5981 
5982  // verify that by reserving we save allocations
5983  {
5984  ryml::EventHandlerTree evt_handler;
5985  ryml::Parser parser(&evt_handler); // reuse a parser
5986  ryml::Tree tree; // reuse a tree
5987 
5988  tree.reserve(10); // reserve the number of nodes
5989  tree.reserve_arena(100); // reserve the arena size
5990  parser.reserve_stack(10); // reserve the parser depth.
5991 
5992  // since the parser stack uses Small Storage Optimization,
5993  // allocations will only happen with capacities higher than 16.
5994  CHECK(mem.num_allocs == 2); // tree, tree_arena and NOT the parser
5995 
5996  parser.reserve_stack(20); // reserve the parser depth.
5997  CHECK(mem.num_allocs == 3); // tree, tree_arena and now the parser as well
5998 
5999  // verify that no other allocations occur when parsing
6000  size_t size_before = mem.alloc_size;
6001  parse_in_arena(&parser, "", R"([a, b, c, d, {foo: bar, money: pennys}])", &tree);
6002  CHECK(mem.alloc_size == size_before);
6003  CHECK(mem.num_allocs == 3);
6004  }
6005  mem.check_and_reset();
6006 
6007  // restore defaults.
6008  ryml::set_callbacks(defaults);
6009 }
static void s_free(void *mem, size_t len, void *this_)
ryml::Callbacks callbacks()
static void * s_allocate(size_t len, void *, void *this_)
pfn_allocate m_allocate
a pointer to an allocate handler function
Definition: common.hpp:543
pfn_free m_free
a pointer to a free handler function
Definition: common.hpp:544

◆ sample_per_tree_allocator()

void sample_per_tree_allocator ( )

Definition at line 6079 of file quickstart.cpp.

6080 {
6084 
6085  // the trees will use the memory in the resources above,
6086  // with each tree using a separate resource
6087  {
6088  // Watchout: ensure that the lifetime of the callbacks target
6089  // exceeds the lifetime of the tree.
6090  ryml::EventHandlerTree evt_handler(mrp.callbacks());
6091  ryml::Parser parser(&evt_handler);
6092  ryml::Tree tree1(mr1.callbacks());
6093  ryml::Tree tree2(mr2.callbacks());
6094 
6095  ryml::csubstr yml1 = "{a: b}";
6096  ryml::csubstr yml2 = "{c: d, e: f, g: [h, i, 0, 1, 2, 3]}";
6097 
6098  parse_in_arena(&parser, "file1.yml", yml1, &tree1);
6099  parse_in_arena(&parser, "file2.yml", yml2, &tree2);
6100  }
6101 
6102  CHECK(mrp.num_allocs == 0); // YAML depth not large enough to warrant a parser allocation
6103  CHECK(mr1.alloc_size <= mr2.alloc_size); // because yml2 has more nodes
6104 }
an example for a per-tree memory allocator
ryml::Callbacks callbacks() const

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

6114 {
6115  // Static trees may incur a static initialization order
6116  // problem. This happens because a default-constructed tree will
6117  // obtain the callbacks from the current global setting, which may
6118  // not have been initialized due to undefined static
6119  // initialization order:
6120  //
6121  // ERROR! depends on ryml::get_callbacks() which may not have been initialized.
6122  //static ryml::Tree tree;
6123  //
6124  // To work around the issue, declare static callbacks
6125  // to explicitly initialize the static tree:
6126  static ryml::Callbacks callbacks = default_callbacks(); // use default callback members
6127  static ryml::Tree tree(callbacks); // OK
6128  // now you can use the tree as normal:
6129  ryml::parse_in_arena(R"(doe: "a deer, a female deer")", &tree);
6130  CHECK(tree["doe"].val() == "a deer, a female deer");
6131 }
ryml::Callbacks default_callbacks()
a bare-bones implementation of the callbacks

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

6140 {
6141  // NOTE: locations are zero-based. If you intend to show the
6142  // location to a human user, you may want to pre-increment the line
6143  // and column by 1.
6144  ryml::csubstr yaml = ""
6145  "{" "\n"
6146  "aa: contents," "\n"
6147  "foo: [one, [two, three]]" "\n"
6148  "}" "\n"
6149  "";
6150  // A parser is needed to track locations, and it has to be
6151  // explicitly set to do it. Location tracking is disabled by
6152  // default.
6153  ryml::ParserOptions opts = {};
6154  opts.locations(true); // enable locations, default is false
6155  ryml::EventHandlerTree evt_handler = {};
6156  ryml::Parser parser(&evt_handler, opts);
6157  CHECK(parser.options().locations());
6158  // When locations are enabled, the first task while parsing will
6159  // consist of building and caching (in the parser) a
6160  // source-to-node lookup structure to accelerate location lookups.
6161  //
6162  // The cost of building the location accelerator is linear in the
6163  // size of the source buffer. This increased cost is the reason
6164  // for the opt-in requirement. When locations are disabled there
6165  // is no cost.
6166  //
6167  // Building the location accelerator may trigger an allocation,
6168  // but this can and should be avoided by reserving prior to
6169  // parsing:
6170  parser.reserve_locations(50u); // reserve for 50 lines
6171  // Now the structure will be built during parsing:
6172  ryml::Tree tree = parse_in_arena(&parser, "source.yml", yaml);
6173  // After this, we are ready to query the location from the parser:
6174  ryml::Location loc = tree.rootref().location(parser);
6175  // As for the complexity of the query: for large buffers it is
6176  // O(log(numlines)). For short source buffers (30 lines and less),
6177  // it is O(numlines), as a plain linear search is faster in this
6178  // case.
6179  CHECK(parser.location_contents(loc).begins_with("{"));
6180  CHECK(loc.offset == 0u);
6181  CHECK(loc.line == 0u);
6182  CHECK(loc.col == 0u);
6183  // on the next call, we only pay O(log(numlines)) because the
6184  // rebuild is already available:
6185  loc = tree["aa"].location(parser);
6186  CHECK(parser.location_contents(loc).begins_with("aa"));
6187  CHECK(loc.offset == 2u);
6188  CHECK(loc.line == 1u);
6189  CHECK(loc.col == 0u);
6190  // KEYSEQ in flow style: points at the key
6191  loc = tree["foo"].location(parser);
6192  CHECK(parser.location_contents(loc).begins_with("foo"));
6193  CHECK(loc.offset == 16u);
6194  CHECK(loc.line == 2u);
6195  CHECK(loc.col == 0u);
6196  loc = tree["foo"][0].location(parser);
6197  CHECK(parser.location_contents(loc).begins_with("one"));
6198  CHECK(loc.line == 2u);
6199  CHECK(loc.col == 6u);
6200  // SEQ in flow style: location points at the opening '[' (there's no key)
6201  loc = tree["foo"][1].location(parser);
6202  CHECK(parser.location_contents(loc).begins_with("["));
6203  CHECK(loc.line == 2u);
6204  CHECK(loc.col == 11u);
6205  loc = tree["foo"][1][0].location(parser);
6206  CHECK(parser.location_contents(loc).begins_with("two"));
6207  CHECK(loc.line == 2u);
6208  CHECK(loc.col == 12u);
6209  loc = tree["foo"][1][1].location(parser);
6210  CHECK(parser.location_contents(loc).begins_with("three"));
6211  CHECK(loc.line == 2u);
6212  CHECK(loc.col == 17u);
6213  // NOTE. The parser locations always point at the latest buffer to
6214  // be parsed with the parser object, so they must be queried using
6215  // the corresponding latest tree to be parsed. This means that if
6216  // the parser is reused, earlier trees will loose the possibility
6217  // of querying for location. It is undefined behavior to query the
6218  // parser for the location of a node from an earlier tree:
6219  ryml::Tree docval = parse_in_arena(&parser, "docval.yaml", "this is a docval");
6220  // From now on, none of the locations from the previous tree can
6221  // be queried:
6222  //loc = tree.rootref().location(parser); // ERROR, undefined behavior
6223  loc = docval.rootref().location(parser); // OK. this is the latest tree from this parser
6224  CHECK(parser.location_contents(loc).begins_with("this is a docval"));
6225  CHECK(loc.line == 0u);
6226  CHECK(loc.col == 0u);
6227 
6228  // NOTES ABOUT CONTAINER LOCATIONS
6229  ryml::Tree tree2 = parse_in_arena(&parser, "containers.yaml",
6230  "" "\n"
6231  "a new: buffer" "\n"
6232  "to: be parsed" "\n"
6233  "map with key:" "\n"
6234  " first: value" "\n"
6235  " second: value" "\n"
6236  "seq with key:" "\n"
6237  " - first value" "\n"
6238  " - second value" "\n"
6239  " -" "\n"
6240  " - nested first value" "\n"
6241  " - nested second value" "\n"
6242  " -" "\n"
6243  " nested first: value" "\n"
6244  " nested second: value" "\n"
6245  "");
6246  // (Likewise, the docval tree can no longer be used to query.)
6247  //
6248  // For key-less block-style maps, the location of the container
6249  // points at the first child's key. For example, in this case
6250  // the root does not have a key, so its location is taken
6251  // to be at the first child:
6252  loc = tree2.rootref().location(parser);
6253  CHECK(parser.location_contents(loc).begins_with("a new"));
6254  CHECK(loc.offset == 1u);
6255  CHECK(loc.line == 1u);
6256  CHECK(loc.col == 0u);
6257  // note the first child points exactly at the same place:
6258  loc = tree2["a new"].location(parser);
6259  CHECK(parser.location_contents(loc).begins_with("a new"));
6260  CHECK(loc.offset == 1u);
6261  CHECK(loc.line == 1u);
6262  CHECK(loc.col == 0u);
6263  loc = tree2["to"].location(parser);
6264  CHECK(parser.location_contents(loc).begins_with("to"));
6265  CHECK(loc.line == 2u);
6266  CHECK(loc.col == 0u);
6267  // but of course, if the block-style map is a KEYMAP, then the
6268  // location is the map's key, and not the first child's key:
6269  loc = tree2["map with key"].location(parser);
6270  CHECK(parser.location_contents(loc).begins_with("map with key"));
6271  CHECK(loc.line == 3u);
6272  CHECK(loc.col == 0u);
6273  loc = tree2["map with key"]["first"].location(parser);
6274  CHECK(parser.location_contents(loc).begins_with("first"));
6275  CHECK(loc.line == 4u);
6276  CHECK(loc.col == 2u);
6277  loc = tree2["map with key"]["second"].location(parser);
6278  CHECK(parser.location_contents(loc).begins_with("second"));
6279  CHECK(loc.line == 5u);
6280  CHECK(loc.col == 2u);
6281  // same thing for KEYSEQ:
6282  loc = tree2["seq with key"].location(parser);
6283  CHECK(parser.location_contents(loc).begins_with("seq with key"));
6284  CHECK(loc.line == 6u);
6285  CHECK(loc.col == 0u);
6286  loc = tree2["seq with key"][0].location(parser);
6287  CHECK(parser.location_contents(loc).begins_with("first value"));
6288  CHECK(loc.line == 7u);
6289  CHECK(loc.col == 4u);
6290  loc = tree2["seq with key"][1].location(parser);
6291  CHECK(parser.location_contents(loc).begins_with("second value"));
6292  CHECK(loc.line == 8u);
6293  CHECK(loc.col == 4u);
6294  // SEQ nested in SEQ: container location points at the first child's "- " dash
6295  loc = tree2["seq with key"][2].location(parser);
6296  CHECK(parser.location_contents(loc).begins_with("- nested first value"));
6297  CHECK(loc.line == 10u);
6298  CHECK(loc.col == 4u);
6299  loc = tree2["seq with key"][2][0].location(parser);
6300  CHECK(parser.location_contents(loc).begins_with("nested first value"));
6301  CHECK(loc.line == 10u);
6302  CHECK(loc.col == 6u);
6303  // MAP nested in SEQ: same as above: point to key
6304  loc = tree2["seq with key"][3].location(parser);
6305  CHECK(parser.location_contents(loc).begins_with("nested first: "));
6306  CHECK(loc.line == 13u);
6307  CHECK(loc.col == 4u);
6308  loc = tree2["seq with key"][3][0].location(parser);
6309  CHECK(parser.location_contents(loc).begins_with("nested first: "));
6310  CHECK(loc.line == 13u);
6311  CHECK(loc.col == 4u);
6312 }