rapidyaml 0.15.2
parse and emit YAML, and do it fast
Loading...
Searching...
No Matches
Quickstart

Example code for every feature. More...

Topics

 Sample helpers
 Helper utilities used in the sample.

Functions

void sample_lightning_overview ()
 a lightning tour over most features see sample_quick_overview
void sample_quick_overview ()
 a brief tour over most features
void sample_substr ()
 demonstrate usage of ryml::substr and ryml::csubstr
void sample_parse_file ()
 demonstrate how to load a YAML file from disk to parse with ryml.
void sample_parse_in_place ()
 demonstrate in-place parsing of a mutable YAML source buffer.
void sample_parse_in_arena ()
 demonstrate parsing of a read-only YAML source buffer
void sample_parse_reuse_tree ()
 demonstrate reuse/modification of tree when parsing
void sample_parse_reuse_parser ()
 Demonstrates reuse of an existing parser.
void sample_parse_reuse_tree_and_parser ()
 for ultimate speed when parsing multiple times, reuse both the tree and parser
void sample_iterate_trees ()
 shows how to programatically iterate through trees
void sample_create_trees ()
 shows how to programatically create trees
void sample_tree_arena ()
 demonstrates explicit and implicit interaction with the tree's string arena.
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.
void sample_empty_null_values ()
 Shows how to deal with empty/null values.
void sample_formatting ()
 ryml provides facilities for formatting/deformatting (imported from c4core into the ryml namespace).
void sample_base64 ()
 demonstrates how to read and write base64-encoded blobs.
void sample_user_scalar_types ()
 to add scalar types (ie leaf types converting to/from string), define the functions above for those types.
void sample_user_container_types ()
 shows how to serialize/deserialize container types.
void sample_std_types ()
 demonstrates usage with the std implementations provided by ryml in the ryml_std.hpp header
void sample_float_precision ()
 control precision of serialized floats
void sample_emit_to_container ()
 demonstrates how to emit to a linear container of char
void sample_emit_to_stream ()
 demonstrates how to emit to a stream-like structure
void sample_emit_to_file ()
 demonstrates how to emit to a FILE*
void sample_emit_nested_node ()
 just like parsing into a nested node, you can also emit from a nested node.
void sample_style ()
 query/set/modify node style to control formatting of emitted YAML code.
void sample_style_flow_formatting ()
 Shows how to control formatting of flow styles.
void sample_style_flow_ml_indent ()
 control the indentation of emitted flow multiline containers
void sample_json ()
 shows how to parse and emit JSON.
void sample_anchors_and_aliases ()
 demonstrates usage with anchors and alias references.
void sample_anchors_and_aliases_create ()
 demonstrates how to use the API to programatically create anchors and aliases
void sample_tags ()
void sample_tag_directives ()
void sample_docs ()
void sample_error_handler ()
 demonstrates how to set a custom error handler for ryml
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.
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.
void sample_global_allocator ()
 demonstrates how to set the global allocator for ryml
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
void sample_location_tracking ()
 demonstrates how to obtain the (zero-based) location of a node from a recently parsed tree

Detailed Description

Example code for every feature.

Best seen online at https://rapidyaml.readthedocs.io/v0.15.2/doxygen/

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

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

◆ sample_quick_overview()

void sample_quick_overview ( )

a brief tour over most features

Definition at line 396 of file quickstart.cpp.

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

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

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

◆ 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 first load the file contents into a buffer before parsing with ryml. To help with this you can use the (efficient) helper [file_get_contents](c4::yml::file_get_contents()). See also the analogous file_put_contents

See also
Parse utilities

Definition at line 1839 of file quickstart.cpp.

1840{
1841 const char filename[] = "ryml_example.yml";
1842 std::string 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 ryml::file_put_contents(yaml, filename);
1851
1852 // now we can load it into a std::string (for example):
1853 {
1854 std::string contents = ryml::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 = ryml::file_get_contents<std::vector<char>>(filename);
1864 ryml::Tree tree = ryml::parse_in_place(ryml::to_substr(contents)); // mutable (substr) 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_get_contents(const char *filename, FILE *fp, size_t filesz, void *buf, size_t bufsz)
load a file of specified size from disk into an existing contiguous buffer.
Definition file.hpp:106
void file_put_contents(void const *buf, size_t sz, FILE *file, const char *filename=nullptr)
save a contiguous buffer into a file
Definition file.hpp:57

◆ 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 . Note that Tree implicitly converts to ConstNodeRef.
Definition tree.cpp:73

◆ 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");
2013
2014 // WATCHOUT: parsing into an existing tree will APPEND to it:
2015 ryml::parse_in_arena("{foo2: 12, bar2: [22, 32]}", &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());
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 mroot[3]["more"].set_map();
2066 mroot[3]["beer"].set_seq();
2067 CHECK(mroot[3]["more"].readable());
2068 CHECK(mroot[3]["more"].key() == "more");
2069 CHECK(mroot[3]["more"].is_map());
2070 CHECK(!mroot[3]["more"].is_val());
2071 ryml::parse_in_arena("{vinho verde: Soalheiro, vinho tinto: Redoma 2017}", mroot[3]["more"]);
2072 ryml::parse_in_arena("- Rochefort 10\n- Busch\n- Leffe Rituel", mroot[3]["beer"]);
2073 ryml::parse_in_arena("lots\nof\nwater", mroot[3]["always"]);
2075 "- a" "\n"
2076 "- b" "\n"
2077 "- {x0: 1,x1: 2}" "\n"
2078 "- champagne: Dom Perignon" "\n"
2079 " coffee: Arabica" "\n"
2080 // note these were added:
2081 " more:" "\n"
2082 " vinho verde: Soalheiro" "\n"
2083 " vinho tinto: Redoma 2017" "\n"
2084 " beer:" "\n"
2085 " - Rochefort 10" "\n"
2086 " - Busch" "\n"
2087 " - Leffe Rituel" "\n"
2088 " always: lots of water" "\n"
2089 "");
2090
2091 // can append at the top:
2092 ryml::parse_in_arena("- foo\n- bar\n- baz\n- bat", mroot);
2094 "- a" "\n"
2095 "- b" "\n"
2096 "- {x0: 1,x1: 2}" "\n"
2097 "- champagne: Dom Perignon" "\n"
2098 " coffee: Arabica" "\n"
2099 " more:" "\n"
2100 " vinho verde: Soalheiro" "\n"
2101 " vinho tinto: Redoma 2017" "\n"
2102 " beer:" "\n"
2103 " - Rochefort 10" "\n"
2104 " - Busch" "\n"
2105 " - Leffe Rituel" "\n"
2106 " always: lots of water" "\n"
2107 // note these were added
2108 "- foo" "\n"
2109 "- bar" "\n"
2110 "- baz" "\n"
2111 "- bat" "\n"
2112 "");
2113
2114 // or nested:
2115 ryml::parse_in_arena("[Kasteel Donker]", mroot[3]["beer"]);
2117 "- a" "\n"
2118 "- b" "\n"
2119 "- {x0: 1,x1: 2}" "\n"
2120 "- champagne: Dom Perignon" "\n"
2121 " coffee: Arabica" "\n"
2122 " more:" "\n"
2123 " vinho verde: Soalheiro" "\n"
2124 " vinho tinto: Redoma 2017" "\n"
2125 " beer:" "\n"
2126 " - Rochefort 10" "\n"
2127 " - Busch" "\n"
2128 " - Leffe Rituel" "\n"
2129 " - Kasteel Donker" "\n" // added this
2130 " always: lots of water" "\n"
2131 "- foo" "\n"
2132 "- bar" "\n"
2133 "- baz" "\n"
2134 "- bat" "\n"
2135 "");
2136}
void clear()
clear the tree and zero every node
Definition tree.cpp:338
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:1022
void reserve(id_type node_capacity=RYML_DEFAULT_TREE_CAPACITY)
Definition tree.cpp:300
void clear_arena()
Definition tree.hpp:287

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

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

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

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

◆ sample_iterate_trees()

void sample_iterate_trees ( )

shows how to programatically iterate through trees

See also
Tree utilities
Node classes

Definition at line 2243 of file quickstart.cpp.

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

◆ sample_create_trees()

void sample_create_trees ( )

shows how to programatically create trees

See also
Tree utilities
Node classes

Definition at line 2315 of file quickstart.cpp.

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

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

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

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

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

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

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

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

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

3253{
3254 // let's start by creating a tree with base64 vals and keys
3255 ryml::Tree tree;
3256 tree.rootref().set_map();
3257 struct text_and_base64 { ryml::csubstr text, base64; };
3258 text_and_base64 cases[] = {
3259 {{"Hello, World!"}, {"SGVsbG8sIFdvcmxkIQ=="}},
3260 {{"Brevity is the soul of wit."}, {"QnJldml0eSBpcyB0aGUgc291bCBvZiB3aXQu"}},
3261 {{"All that glitters is not gold."}, {"QWxsIHRoYXQgZ2xpdHRlcnMgaXMgbm90IGdvbGQu"}},
3262 };
3263 // to encode base64 and write the result to val:
3264 for(text_and_base64 c : cases)
3265 tree[c.text] << ryml::fmt::base64(c.text);
3266 // to encode base64 and write the result to key:
3267 for(text_and_base64 c : cases)
3268 tree.rootref().append_child() << ryml::key(ryml::fmt::base64(c.text)) << c.text;
3269 // check the result:
3270 for(text_and_base64 c : cases)
3271 {
3272 CHECK(tree[c.text].val() == c.base64);
3273 CHECK(tree[c.base64].val() == c.text);
3274 }
3275 // and this is how the YAML now looks:
3277 "Hello, World!: SGVsbG8sIFdvcmxkIQ==" "\n"
3278 "Brevity is the soul of wit.: QnJldml0eSBpcyB0aGUgc291bCBvZiB3aXQu" "\n"
3279 "All that glitters is not gold.: QWxsIHRoYXQgZ2xpdHRlcnMgaXMgbm90IGdvbGQu" "\n"
3280 // note that the keys below are base64-encoded
3281 "SGVsbG8sIFdvcmxkIQ==: Hello, World!" "\n"
3282 "QnJldml0eSBpcyB0aGUgc291bCBvZiB3aXQu: Brevity is the soul of wit." "\n"
3283 "QWxsIHRoYXQgZ2xpdHRlcnMgaXMgbm90IGdvbGQu: All that glitters is not gold." "\n"
3284 "");
3285 char buf1_[128], buf2_[128];
3286 ryml::substr buf1 = buf1_; // this is where we will write the result (using >>)
3287 ryml::substr buf2 = buf2_; // this is where we will write the result (using deserialize()/deserialize_key())
3288 // to decode base64 and write the result to buf:
3289 for(const text_and_base64 c : cases)
3290 {
3291 // this decodes base64 and write the decoded result into an
3292 // existing buffer (buf1):
3293 size_t len = 0; // the decoded length
3294 tree[c.text] >> ryml::fmt::base64(buf1, &len);
3295 // The base64() tag function is used to get the
3296 // deserialization using c4::decode_base64(). This will
3297 // respect the limits of the buffer, and fail with an error if
3298 // the buffer is too small (or if the base64 encoding is
3299 // wrong). The optional second parameter is set to the decoded
3300 // size, ie, the length of the decoded result, which is also
3301 // the size required for the buffer.
3302 CHECK(len <= buf1.len);
3303 CHECK(c.text.len == len);
3304 CHECK(buf1.first(len) == c.text);
3305 // likewise for keys:
3306 tree[c.base64] >> ryml::key(ryml::fmt::base64(buf2, &len));
3307 CHECK(len <= buf2.len);
3308 CHECK(buf2.first(len) == c.text);
3309 //
3310 // interop with std::string:
3311 std::string result;
3312 tree[c.text] >> ryml::fmt::base64(result);
3313 CHECK(result == c.text);
3314 // likewise for keys:
3315 tree[c.base64] >> ryml::key(ryml::fmt::base64(result));
3316 CHECK(result == c.text);
3317 }
3318 // directly encode variables: integers
3319 {
3320 const uint64_t valin = UINT64_C(0xdeadbeef);
3321 tree["deadbeef"] << c4::fmt::base64(valin); // sometimes cbase64() is needed to avoid ambiguity
3322 uint64_t valout = 0;
3323 size_t len = 0;
3324 tree["deadbeef"] >> ryml::fmt::base64(valout, &len);
3325 CHECK(len == sizeof(valout));
3326 CHECK(valout == UINT64_C(0xdeadbeef)); // base64 roundtrip is bit-accurate
3327 // also works without length parameter:
3328 valout = {};
3329 tree["deadbeef"] >> ryml::fmt::base64(valout);
3330 CHECK(valout == UINT64_C(0xdeadbeef)); // base64 roundtrip is bit-accurate
3331 }
3332 // directly encode variables: floating point
3333 {
3334 const double valin = 123456.7891011;
3335 tree["float"] << c4::fmt::base64(valin);
3336 double valout = 0;
3337 size_t len = 0;
3338 tree["float"] >> ryml::fmt::base64(valout, &len);
3339 CHECK(len == sizeof(valout));
3340 CHECK(memcmp(&valout, &valin, sizeof(valout)) == 0); // base64 roundtrip is bit-accurate // NOLINT
3341 // also works without length parameter:
3342 valout = {};
3343 tree["float"] >> ryml::fmt::base64(valout);
3344 CHECK(memcmp(&valout, &valin, sizeof(valout)) == 0); // base64 roundtrip is bit-accurate // NOLINT
3345 }
3346 // directly encode memory ranges
3347 {
3348 const uint32_t data_in[11] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xdeadbeef};
3349 uint32_t data_out[11] = {};
3350 tree["int_data"] << ryml::fmt::base64(data_in, 11);
3351 CHECK(memcmp(data_in, data_out, sizeof(data_in)) != 0); // before the roundtrip
3352 size_t len = 0;
3353 tree["int_data"] >> ryml::fmt::base64(data_out, 11, &len);
3354 CHECK(len == sizeof(data_out));
3355 CHECK(memcmp(data_in, data_out, sizeof(data_in)) == 0); // after the roundtrip
3356 // also works without length parameter:
3357 memset(data_out, 0, sizeof(data_out));
3358 tree["int_data"] >> ryml::fmt::base64(data_out);
3359 CHECK(memcmp(data_in, data_out, sizeof(data_in)) == 0); // after the roundtrip
3360 }
3361}

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

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

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

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

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

◆ sample_float_precision()

void sample_float_precision ( )

control precision of serialized floats

Definition at line 3908 of file quickstart.cpp.

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

◆ sample_emit_to_container()

void sample_emit_to_container ( )

demonstrates how to emit to a linear container of char

Definition at line 4045 of file quickstart.cpp.

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

◆ sample_emit_to_stream()

void sample_emit_to_stream ( )

demonstrates how to emit to a stream-like structure

Definition at line 4173 of file quickstart.cpp.

4174{
4175 ryml::csubstr ymlb =
4176 "- a" "\n"
4177 "- b" "\n"
4178 "- x0: 1" "\n"
4179 " x1: 2" "\n"
4180 "- champagne: Dom Perignon" "\n"
4181 " coffee: Arabica" "\n"
4182 " more:" "\n"
4183 " vinho verde: Soalheiro" "\n"
4184 " vinho tinto: Redoma 2017" "\n"
4185 " beer:" "\n"
4186 " - Rochefort 10" "\n"
4187 " - Busch" "\n"
4188 " - Leffe Rituel" "\n"
4189 "- foo" "\n"
4190 "- bar" "\n"
4191 "- baz" "\n"
4192 "- bat" "\n"
4193 "";
4194 const ryml::Tree tree = ryml::parse_in_arena(ymlb);
4195
4196 std::string s;
4197
4198 // emit a full tree
4199 {
4200 std::stringstream ss;
4201 ss << tree; // works with any stream having .operator<<() and .write()
4202 s = ss.str();
4203 CHECK(ryml::to_csubstr(s) == ymlb);
4204 }
4205
4206 // emit a full tree as json
4207 {
4208 std::stringstream ss;
4209 ss << ryml::as_json(tree); // works with any stream having .operator<<() and .write()
4210 s = ss.str();
4211 CHECK(s ==
4212 "[" "\n"
4213 " \"a\"," "\n"
4214 " \"b\"," "\n"
4215 " {" "\n"
4216 " \"x0\": 1," "\n"
4217 " \"x1\": 2" "\n"
4218 " }," "\n"
4219 " {" "\n"
4220 " \"champagne\": \"Dom Perignon\"," "\n"
4221 " \"coffee\": \"Arabica\"," "\n"
4222 " \"more\": {" "\n"
4223 " \"vinho verde\": \"Soalheiro\"," "\n"
4224 " \"vinho tinto\": \"Redoma 2017\"" "\n"
4225 " }," "\n"
4226 " \"beer\": [" "\n"
4227 " \"Rochefort 10\"," "\n"
4228 " \"Busch\"," "\n"
4229 " \"Leffe Rituel\"" "\n"
4230 " ]" "\n"
4231 " }," "\n"
4232 " \"foo\"," "\n"
4233 " \"bar\"," "\n"
4234 " \"baz\"," "\n"
4235 " \"bat\"" "\n"
4236 "]" "\n"
4237 "");
4238 }
4239
4240 // emit a nested node
4241 {
4242 std::stringstream ss;
4243 ss << tree[3][2]; // works with any stream having .operator<<() and .write()
4244 s = ss.str();
4245 CHECK(s == ""
4246 "more:" "\n"
4247 " vinho verde: Soalheiro" "\n"
4248 " vinho tinto: Redoma 2017" "\n"
4249 "");
4250 }
4251
4252 // emit a nested node as json
4253 {
4254 std::stringstream ss;
4255 ss << ryml::as_json(tree[3][2]); // works with any stream having .operator<<() and .write()
4256 s = ss.str();
4258 "\"more\": {" "\n"
4259 " \"vinho verde\": \"Soalheiro\"," "\n"
4260 " \"vinho tinto\": \"Redoma 2017\"" "\n"
4261 "}" "\n"
4262 "");
4263 }
4264}
tag type to mark a tree or node to be emitted as yaml when using operator<<, with options.

◆ sample_emit_to_file()

void sample_emit_to_file ( )

demonstrates how to emit to a FILE*

Definition at line 4270 of file quickstart.cpp.

4271{
4272 ryml::csubstr yml = ""
4273 "- a" "\n"
4274 "- b" "\n"
4275 "- x0: 1" "\n"
4276 " x1: 2" "\n"
4277 "- champagne: Dom Perignon" "\n"
4278 " coffee: Arabica" "\n"
4279 " more:" "\n"
4280 " vinho verde: Soalheiro" "\n"
4281 " vinho tinto: Redoma 2017" "\n"
4282 " beer:" "\n"
4283 " - Rochefort 10" "\n"
4284 " - Busch" "\n"
4285 " - Leffe Rituel" "\n"
4286 "- foo" "\n"
4287 "- bar" "\n"
4288 "- baz" "\n"
4289 "- bat" "\n"
4290 "";
4291 const ryml::Tree tree = ryml::parse_in_arena(yml);
4292 // this is emitting to stdout, but of course you can pass in any
4293 // FILE* obtained from fopen()
4294 ryml::emit_yaml(tree, tree.root_id(), stdout);
4295}

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

4302{
4303 const ryml::Tree tree = ryml::parse_in_arena(""
4304 "- a" "\n"
4305 "- b" "\n"
4306 "- x0: 1" "\n"
4307 " x1: 2" "\n"
4308 "- champagne: Dom Perignon" "\n"
4309 " coffee: Arabica" "\n"
4310 " more:" "\n"
4311 " vinho verde: Soalheiro" "\n"
4312 " vinho tinto: Redoma 2017" "\n"
4313 " beer:" "\n"
4314 " - Rochefort 10" "\n"
4315 " - Busch" "\n"
4316 " - Leffe Rituel" "\n"
4317 " - - and so" "\n"
4318 " - many other" "\n"
4319 " - wonderful beers" "\n"
4320 "- more" "\n"
4321 "- seq" "\n"
4322 "- members" "\n"
4323 "- here" "\n"
4324 "");
4325 // Let's now emit the beer node. Note that its key is also
4326 // emitted, making the result a map with a single child:
4327 CHECK(ryml::emitrs_yaml<std::string>(tree[3]["beer"]) == ""
4328 "beer:" "\n"
4329 " - Rochefort 10" "\n"
4330 " - Busch" "\n"
4331 " - Leffe Rituel" "\n"
4332 " - - and so" "\n"
4333 " - many other" "\n"
4334 " - wonderful beers" "\n"
4335 "");
4336 // You can use EmitOptions to prevent the key from being
4337 // emitted. Note how the result is now just the contents without
4338 // the key, and therefore the root is now a seq:
4339 auto without_key = ryml::EmitOptions{}.emit_nonroot_key(false);
4340 CHECK(ryml::emitrs_yaml<std::string>(tree[3]["beer"], without_key) == ""
4341 "- Rochefort 10" "\n"
4342 "- Busch" "\n"
4343 "- Leffe Rituel" "\n"
4344 "- - and so" "\n"
4345 " - many other" "\n"
4346 " - wonderful beers" "\n"
4347 "");
4348 // For sequences, the behavior is opposite. Notice how the leading
4349 // dash defaults to omit:
4350 CHECK(ryml::emitrs_yaml<std::string>(tree[3]["beer"][3]) == ""
4351 "- and so" "\n"
4352 "- many other" "\n"
4353 "- wonderful beers" "\n"
4354 "");
4355 // And likewise, you can use EmitOptions to force the dash:
4356 auto with_dash = ryml::EmitOptions{}.emit_nonroot_dash(true);
4357 CHECK(ryml::emitrs_yaml<std::string>(tree[3]["beer"][3], with_dash) == ""
4358 "- - and so" "\n" // note the added dash and indentation
4359 " - many other" "\n"
4360 " - wonderful beers" "\n"
4361 "");
4362 // Example: emit a scalar node (seq member):
4363 CHECK(ryml::emitrs_yaml<std::string>(tree[3]["beer"][0]) == "Rochefort 10");
4364 CHECK(ryml::emitrs_yaml<std::string>(tree[3]["beer"][0], with_dash) == "- Rochefort 10\n");
4365 // Example: emit a scalar node (map member):
4366 CHECK(ryml::emitrs_yaml<std::string>(tree[3]["more"][0]) == "vinho verde: Soalheiro\n");
4367 CHECK(ryml::emitrs_yaml<std::string>(tree[3]["more"][0], without_key) == "Soalheiro");
4368}
A lightweight object containing options to be used when emitting.
EmitOptions & emit_nonroot_key(bool enabled) noexcept
When emit starts on a node which is not the root node, emit the node key as well.
EmitOptions & emit_nonroot_dash(bool enabled) noexcept
When emit starts on a node which is not the root node, emit a leading dash.

◆ sample_style()

void sample_style ( )

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

See also
See more details about formatting flow containers in sample_style_flow_formatting() (below).

see also:

Definition at line 4375 of file quickstart.cpp.

4376{
4377 // we will be using these helpers throughout this function
4378 auto tostr = [](ryml::ConstNodeRef n) {
4380 };
4381 auto tostr_opts = [](ryml::ConstNodeRef n, ryml::EmitOptions opts) {
4382 return ryml::emitrs_yaml<std::string>(n, opts);
4383 };
4384 // let's parse this yaml:
4385 ryml::csubstr yaml = ""
4386 "block map:" "\n"
4387 " block key: block val" "\n"
4388 "block seq:" "\n"
4389 " - block val 1" "\n"
4390 " - block val 2" "\n"
4391 " - 'quoted'" "\n"
4392 "flow map, singleline: {flow key: flow val}" "\n"
4393 "flow seq, singleline: [flow val,flow val]" "\n"
4394 "flow map, multiline: {" "\n"
4395 " flow key: flow val" "\n"
4396 " }" "\n"
4397 "flow seq, multiline: [" "\n"
4398 " flow val," "\n"
4399 " flow val" "\n"
4400 " ]" "\n"
4401 "";
4402 ryml::Tree tree = ryml::parse_in_arena(yaml);
4403 // while parsing, ryml marks parsed nodes with their original style:
4404 CHECK(tree.rootref().is_block());
4405 CHECK(tree["block map"].is_key_plain());
4406 CHECK(tree["block seq"].is_key_plain());
4407 CHECK(tree["flow map, singleline"].is_key_plain());
4408 CHECK(tree["flow seq, singleline"].is_key_plain());
4409 CHECK(tree["flow map, multiline"].is_key_plain());
4410 CHECK(tree["flow seq, multiline"].is_key_plain());
4411 CHECK(tree["block map"].is_block());
4412 CHECK(tree["block seq"].is_block());
4413 // flow is either singleline (FLOW_SL) or multiline (FLOW_ML1)
4414 CHECK(tree["flow map, singleline"].is_flow_sl());
4415 CHECK(tree["flow seq, singleline"].is_flow_sl());
4416 CHECK(tree["flow map, multiline"].is_flow_ml1());
4417 CHECK(tree["flow seq, multiline"].is_flow_ml1());
4418 // is_flow() is equivalent to (is_flow_sl() || is_flow_ml1() || is_flow_mln())
4419 CHECK(tree["flow map, singleline"].is_flow());
4420 CHECK(tree["flow seq, singleline"].is_flow());
4421 CHECK(tree["flow map, multiline"].is_flow());
4422 CHECK(tree["flow seq, multiline"].is_flow());
4423 //
4424 // since the tree nodes are marked with their original parsed
4425 // style, emitting the parsed tree will preserve the original
4426 // style (minus whitespace):
4427 //
4428 CHECK(tostr(tree) == yaml); // same as before!
4429 //
4430 // you can set/modify the style programatically!
4431 //
4432 // here are more examples.
4433 //
4434 {
4435 ryml::NodeRef n = tree["block map"]; // Let's look at one node
4436 // It looks like this originally:
4437 CHECK(tostr(n) ==
4438 "block map:\n"
4439 " block key: block val\n"
4440 "");
4441 // let's modify its style:
4442 n.set_key_style(ryml::KEY_SQUO); // scalar style: to single-quoted scalar
4443 n.set_container_style(ryml::FLOW_SL); // container style: to flow singleline
4444 // now it looks like this:
4445 CHECK(tostr(n) ==
4446 "'block map': {block key: block val}\n"
4447 "");
4448 }
4449 // next example
4450 {
4451 ryml::NodeRef n = tree["block seq"];
4452 CHECK(tostr(n) == ""
4453 "block seq:\n"
4454 " - block val 1\n"
4455 " - block val 2\n"
4456 " - 'quoted'\n"
4457 "");
4458 n.set_key_style(ryml::KEY_DQUO); // scalar style: to double-quoted scalar
4459 n[2].set_val_style(ryml::VAL_PLAIN); // scalar style: to plain
4460 n.set_container_style(ryml::FLOW_MLN); // container style: to flow multiline, N values per line
4461 CHECK(tostr(n) == ""
4462 "\"block seq\": [\n"
4463 " block val 1,block val 2,quoted\n"
4464 " ]\n");
4465 n.set_container_style(ryml::FLOW_MLN|ryml::FLOW_SPC); // force space after comma
4466 CHECK(tostr(n) == ""
4467 "\"block seq\": [\n"
4468 " block val 1, block val 2, quoted\n"
4469 " ]\n");
4470 auto maxcols20 = ryml::EmitOptions{}.max_cols(20); // set the max number of cols for FLOW_MLN
4471 CHECK(tostr_opts(n, maxcols20) == ""
4472 "\"block seq\": [\n"
4473 " block val 1, block val 2,\n"
4474 " quoted\n"
4475 " ]\n");
4476 n.set_container_style(ryml::FLOW_MLN); // no spaces now
4477 CHECK(tostr_opts(n, maxcols20) == ""
4478 "\"block seq\": [\n"
4479 " block val 1,block val 2,\n"
4480 " quoted\n"
4481 " ]\n");
4482 n.set_container_style(ryml::FLOW_SL); // to flow singleline
4483 CHECK(tostr(n) == ""
4484 "\"block seq\": [block val 1,block val 2,quoted]\n");
4485 n.set_container_style(ryml::FLOW_SL|ryml::FLOW_SPC); // now with space after comma
4486 CHECK(tostr(n) == ""
4487 "\"block seq\": [block val 1, block val 2, quoted]\n");
4488 n.set_container_style(ryml::FLOW_ML1); // to flow multiline, 1 value per line
4489 CHECK(tostr(n) == ""
4490 "\"block seq\": [\n"
4491 " block val 1,\n"
4492 " block val 2,\n"
4493 " quoted\n"
4494 " ]\n");
4495 /// @see See more details about formatting flow containers in
4496 /// @ref sample_style_flow_formatting() (below).
4497 }
4498 // next example
4499 {
4500 ryml::NodeRef n = tree["flow map, singleline"];
4501 CHECK(tostr(n) == "flow map, singleline: {flow key: flow val}\n");
4503 n["flow key"].set_val_style(ryml::VAL_LITERAL);
4504 CHECK(tostr(n) == ""
4505 "flow map, singleline:\n"
4506 " flow key: |-\n"
4507 " flow val\n"
4508 "");
4509 }
4510 // next example
4511 {
4512 ryml::NodeRef n = tree["flow map, multiline"];
4513 CHECK(tostr(n) == ""
4514 "flow map, multiline: {\n"
4515 " flow key: flow val\n"
4516 " }\n"
4517 "");
4519 CHECK(tostr(n) == ""
4520 "flow map, multiline:\n"
4521 " flow key: flow val\n"
4522 "");
4523 }
4524 // next example
4525 {
4526 ryml::NodeRef n = tree["flow seq, singleline"];
4527 CHECK(tostr(n) == "flow seq, singleline: [flow val,flow val]\n");
4532 CHECK(tostr(n) == ""
4533 "? >-\n"
4534 " flow seq, singleline\n"
4535 ":\n"
4536 " - 'flow val'\n"
4537 " - \"flow val\"\n"
4538 "");
4539 }
4540 // next example
4541 {
4542 ryml::NodeRef n = tree["flow seq, multiline"];
4543 CHECK(tostr(n) == ""
4544 "flow seq, multiline: [\n"
4545 " flow val,\n"
4546 " flow val\n"
4547 " ]\n"
4548 "");
4550 CHECK(tostr(n) == "flow seq, multiline: [flow val,flow val]\n");
4551 }
4552 // note the full tree now:
4553 CHECK(tostr(tree) != yaml);
4554 CHECK(tostr(tree) ==
4555 "'block map': {block key: block val}" "\n"
4556 "\"block seq\": [" "\n"
4557 " block val 1," "\n"
4558 " block val 2," "\n"
4559 " quoted" "\n"
4560 " ]" "\n"
4561 "flow map, singleline:" "\n"
4562 " flow key: |-" "\n"
4563 " flow val" "\n"
4564 "? >-" "\n"
4565 " flow seq, singleline" "\n"
4566 ":" "\n"
4567 " - 'flow val'" "\n"
4568 " - \"flow val\"" "\n"
4569 "flow map, multiline:" "\n"
4570 " flow key: flow val" "\n"
4571 "flow seq, multiline: [flow val,flow val]" "\n"
4572 "");
4573 // you can clear the style of single nodes:
4574 tree["block map"].clear_style();
4575 tree["block seq"].clear_style();
4576 CHECK(tostr(tree) ==
4577 "block map:" "\n"
4578 " block key: block val" "\n"
4579 "block seq:" "\n"
4580 " - block val 1" "\n"
4581 " - block val 2" "\n"
4582 " - quoted" "\n"
4583 "flow map, singleline:" "\n"
4584 " flow key: |-" "\n"
4585 " flow val" "\n"
4586 "? >-" "\n"
4587 " flow seq, singleline" "\n"
4588 ":" "\n"
4589 " - 'flow val'" "\n"
4590 " - \"flow val\"" "\n"
4591 "flow map, multiline:" "\n"
4592 " flow key: flow val" "\n"
4593 "flow seq, multiline: [flow val,flow val]" "\n"
4594 "");
4595 // you can clear the style recursively:
4596 tree.rootref().clear_style(/*recurse*/true);
4597 // when emitting nodes which have no style set, ryml will default
4598 // to block format for containers, and call
4599 // ryml::scalar_style_choose() to pick the style for each scalar
4600 // (at the cost of a scan over each scalar). Note that ryml picks
4601 // single-quoted for scalars containing commas:
4602 CHECK(tostr(tree) ==
4603 "block map:" "\n"
4604 " block key: block val" "\n"
4605 "block seq:" "\n"
4606 " - block val 1" "\n"
4607 " - block val 2" "\n"
4608 " - quoted" "\n"
4609 "flow map, singleline:" "\n"
4610 " flow key: flow val" "\n"
4611 "flow seq, singleline:" "\n"
4612 " - flow val" "\n"
4613 " - flow val" "\n"
4614 "flow map, multiline:" "\n"
4615 " flow key: flow val" "\n"
4616 "flow seq, multiline:" "\n"
4617 " - flow val" "\n"
4618 " - flow val" "\n"
4619 "");
4620 // you can set the style based on type conditions:
4621 //
4622 // eg, set a single key to single-quoted
4623 tree["block map"].set_style_conditionally(/*type_mask*/ryml::KEY,
4624 /*remflags*/ryml::KEY_STYLE,
4625 /*addflags*/ryml::KEY_SQUO,
4626 /*recurse*/false);
4627 CHECK(tostr(tree) ==
4628 "'block map':" "\n"
4629 " block key: block val" "\n"
4630 "block seq:" "\n"
4631 " - block val 1" "\n"
4632 " - block val 2" "\n"
4633 " - quoted" "\n"
4634 "flow map, singleline:" "\n"
4635 " flow key: flow val" "\n"
4636 "flow seq, singleline:" "\n"
4637 " - flow val" "\n"
4638 " - flow val" "\n"
4639 "flow map, multiline:" "\n"
4640 " flow key: flow val" "\n"
4641 "flow seq, multiline:" "\n"
4642 " - flow val" "\n"
4643 " - flow val" "\n"
4644 "");
4645 // change all keys to single-quoted:
4646 tree.rootref().set_style_conditionally(/*type_mask*/ryml::KEY,
4647 /*remflags*/ryml::KEY_STYLE,
4648 /*addflags*/ryml::KEY_SQUO,
4649 /*recurse*/true);
4650 // change all vals to double-quoted
4651 tree.rootref().set_style_conditionally(/*type_mask*/ryml::VAL,
4652 /*remflags*/ryml::VAL_STYLE,
4653 /*addflags*/ryml::VAL_DQUO,
4654 /*recurse*/true);
4655 // change all seqs to flow
4656 tree.rootref().set_style_conditionally(/*type_mask*/ryml::SEQ,
4657 /*remflags*/ryml::CONTAINER_STYLE,
4658 /*addflags*/ryml::FLOW_SL,
4659 /*recurse*/true);
4660 // change all maps to flow
4661 tree.rootref().set_style_conditionally(/*type_mask*/ryml::MAP,
4662 /*remflags*/ryml::CONTAINER_STYLE,
4663 /*addflags*/ryml::BLOCK,
4664 /*recurse*/true);
4665 // done!
4666 CHECK(tostr(tree) == ""
4667 "'block map':" "\n"
4668 " 'block key': \"block val\"" "\n"
4669 "'block seq': [\"block val 1\",\"block val 2\",\"quoted\"]" "\n"
4670 "'flow map, singleline':" "\n"
4671 " 'flow key': \"flow val\"" "\n"
4672 "'flow seq, singleline': [\"flow val\",\"flow val\"]" "\n"
4673 "'flow map, multiline':" "\n"
4674 " 'flow key': \"flow val\"" "\n"
4675 "'flow seq, multiline': [\"flow val\",\"flow val\"]" "\n"
4676 "");
4677 // you can also set a conditional style in a single node (or its branch if recurse is true):
4678 tree["flow seq, singleline"].set_style_conditionally(/*type_mask*/ryml::SEQ,
4679 /*remflags*/ryml::CONTAINER_STYLE,
4680 /*addflags*/ryml::BLOCK,
4681 /*recurse*/false);
4682 CHECK(tostr(tree) == ""
4683 "'block map':" "\n"
4684 " 'block key': \"block val\"" "\n"
4685 "'block seq': [\"block val 1\",\"block val 2\",\"quoted\"]" "\n"
4686 "'flow map, singleline':" "\n"
4687 " 'flow key': \"flow val\"" "\n"
4688 "'flow seq, singleline':" "\n"
4689 " - \"flow val\"" "\n"
4690 " - \"flow val\"" "\n"
4691 "'flow map, multiline':" "\n"
4692 " 'flow key': \"flow val\"" "\n"
4693 "'flow seq, multiline': [\"flow val\",\"flow val\"]" "\n"
4694 "");
4695 /// see also:
4696 /// - @ref ryml::scalar_style_choose_block()
4697 /// - @ref ryml::scalar_style_choose_flow()
4698 /// - @ref ryml::scalar_style_choose_json()
4699 /// - @ref ryml::scalar_style_query_squo()
4700 /// - @ref ryml::scalar_style_query_plain()
4701}
void set_key_style(NodeType_e style)
Definition node.hpp:973
void set_style_conditionally(NodeType type_mask, NodeType rem_style_flags, NodeType add_style_flags, bool recurse=false)
Definition node.hpp:1016
void clear_style(bool recurse=false)
Definition node.hpp:1010
void set_val_style(NodeType_e style)
Definition node.hpp:974
void set_container_style(NodeType_e style)
Definition node.hpp:972
void clear_style(id_type node, bool recurse=false)
Definition tree.cpp:1408
void set_style_conditionally(id_type node, NodeType type_mask, NodeType rem_style_flags, NodeType add_style_flags, bool recurse=false)
Definition tree.cpp:1418
@ KEY_DQUO
mark key scalar as double quoted "
@ MAP
a map: a parent of KEYVAL/KEYSEQ/KEYMAP nodes
Definition node_type.hpp:39
@ KEY
the scalar to the left of : in a map's member
Definition node_type.hpp:37
@ FLOW_ML1
mark container with multi-line flow style, 1 element per line
Definition node_type.hpp:79
@ VAL_STYLE
mask of VALQUO|VAL_PLAIN : all the val scalar styles for val (not container styles!...
@ FLOW_SL
mark container with single-line flow style
Definition node_type.hpp:60
@ 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
@ FLOW_MLN
mark container with multi-line flow style, n elements per line, wrapped (as set by EmitOptions::max_c...
Definition node_type.hpp:94
@ SEQ
a seq: a parent of VAL/SEQ/MAP nodes
Definition node_type.hpp:40
@ VAL_SQUO
mark val scalar as single quoted '
@ KEY_STYLE
mask of KEYQUO|KEY_PLAIN : all the key scalar styles for key (not container styles!...
@ VAL_PLAIN
mark val scalar as plain scalar (unquoted, even when multiline)
@ BLOCK
mark container with block style
@ FLOW_SPC
mark container with spaces after comma when in flow mode. Applies to both FLOW_SL and FLOW_MLN (but n...
@ VAL_DQUO
mark val scalar as double quoted "
@ CONTAINER_STYLE
mask of CONTAINER_STYLE_FLOW|CONTAINER_STYLE_BLOCK : all container style flags
@ KEY_SQUO
mark key scalar as single quoted '
@ VAL_LITERAL
mark val scalar as multiline, block literal |
@ KEY_FOLDED
mark key scalar as multiline, block folded >
EmitOptions & max_cols(id_type cols) noexcept
Set max columns for the emitted YAML in FLOW_MLN mode.
bool is_block() const RYML_NOEXCEPT
Forward to Tree::is_block().
Definition node.hpp:266

◆ sample_style_flow_formatting()

void sample_style_flow_formatting ( )

Shows how to control formatting of flow styles.

Definition at line 4707 of file quickstart.cpp.

4708{
4709 // we will be using this helper throughout this function
4710 auto tostr = [](ryml::ConstNodeRef n, ryml::EmitOptions opts) {
4711 return ryml::emitrs_yaml<std::string>(n, opts);
4712 };
4713 auto tostr_json = [](ryml::ConstNodeRef n, ryml::EmitOptions opts) {
4714 return ryml::emitrs_json<std::string>(n, opts);
4715 };
4716 const ryml::EmitOptions emit_defaults = ryml::EmitOptions{};
4717 // let's parse this, which is in FLOW_ML1 (flow multiline, 1 value per line):
4718 ryml::csubstr yaml = ""
4719 "{" "\n"
4720 " map: {" "\n"
4721 " seq: [" "\n"
4722 " 0," "\n"
4723 " 1," "\n"
4724 " 2," "\n"
4725 " 3," "\n"
4726 " [40,41]" "\n"
4727 " ]" "\n"
4728 " }" "\n"
4729 "}" "\n"
4730 "";
4731 // note that the parser defaults to detecting multiline flow
4732 // (FLOW_ML1) containers:
4733 {
4734 const ryml::Tree tree = ryml::parse_in_arena(yaml);
4735 CHECK(tree["map"].is_flow_ml1()); // etc
4736 CHECK(tree["map"]["seq"].is_flow_ml1()); // etc
4737 CHECK(tree["map"]["seq"][4].is_flow_sl()); // etc
4738 // emitted yaml is exactly equal to parsed yaml:
4739 CHECK(tostr(tree, emit_defaults) == yaml);
4740 // json looks like similar:
4741 CHECK(tostr_json(tree, emit_defaults) ==
4742 "{\n"
4743 " \"map\": {\n"
4744 " \"seq\": [\n"
4745 " 0,\n"
4746 " 1,\n"
4747 " 2,\n"
4748 " 3,\n"
4749 " [40,41]\n"
4750 " ]\n"
4751 " }\n"
4752 "}\n"
4753 "");
4754 }
4755 // if you prefer to shorten the emitted yaml, you can set the
4756 // parser to disable flow multiline detection. It will then pick
4757 // singleline flow (FLOW_SL) for all flow containers:
4758 {
4760 .detect_flow_ml(false);
4761 const ryml::Tree tree = ryml::parse_in_arena(yaml, opts);
4762 CHECK(tree["map"].is_flow_sl()); // etc
4763 // notice how this is smaller now:
4764 CHECK(tostr(tree, emit_defaults) ==
4765 "{map: {seq: [0,1,2,3,[40,41]]}}");
4766 // and json as well
4767 CHECK(tostr_json(tree, emit_defaults) ==
4768 "{\"map\": {\"seq\": [0,1,2,3,[40,41]]}}");
4769 // you can also force spaces everywhere without adding
4770 // FLOW_SPC in individual containers:
4771 const ryml::EmitOptions with_spaces = ryml::EmitOptions{}
4772 .force_flow_spc(true);
4773 CHECK(tostr(tree, with_spaces) ==
4774 "{map: {seq: [0, 1, 2, 3, [40, 41]]}}");
4775 // and json as well
4776 CHECK(tostr_json(tree, with_spaces) ==
4777 "{\"map\": {\"seq\": [0, 1, 2, 3, [40, 41]]}}");
4778 }
4779 // or you can still have the default detection of flow_ml, but set
4780 // it to pick FLOW_MLN (multiline, n values per line), instead of
4781 // the default FLOW_ML1 (multiline, 1 values per line)
4782 {
4785 const ryml::Tree tree = ryml::parse_in_arena(yaml, opts);
4786 CHECK(tree["map"].is_flow_mln());
4787 CHECK(tree["map"]["seq"][4].is_flow_sl()); // [40,41] is FLOW_SL
4788 CHECK(tostr(tree, emit_defaults) ==
4789 "{" "\n"
4790 " map: {" "\n"
4791 " seq: [" "\n"
4792 " 0,1,2,3,[40,41]" "\n"
4793 " ]" "\n"
4794 " }" "\n"
4795 "}" "\n");
4796 CHECK(tostr_json(tree, emit_defaults) ==
4797 "{" "\n"
4798 " \"map\": {" "\n"
4799 " \"seq\": [" "\n"
4800 " 0,1,2,3,[40,41]" "\n"
4801 " ]" "\n"
4802 " }" "\n"
4803 "}" "\n");
4804 // now with spaces:
4805 const ryml::EmitOptions with_spaces = ryml::EmitOptions{}
4806 .force_flow_spc(true);
4807 CHECK(tostr(tree, with_spaces) ==
4808 "{" "\n"
4809 " map: {" "\n"
4810 " seq: [" "\n"
4811 " 0, 1, 2, 3, [40, 41]" "\n"
4812 " ]" "\n"
4813 " }" "\n"
4814 "}" "\n");
4815 CHECK(tostr_json(tree, with_spaces) ==
4816 "{" "\n"
4817 " \"map\": {" "\n"
4818 " \"seq\": [" "\n"
4819 " 0, 1, 2, 3, [40, 41]" "\n"
4820 " ]" "\n"
4821 " }" "\n"
4822 "}" "\n");
4823 }
4824 // you can also disable indentation of both FLOW_ML1 and FLOW_MLN
4825 // (see more details in @ref sample_style_flow_ml_indent())
4826 {
4827 const ryml::EmitOptions noindent = ryml::EmitOptions{}
4828 .indent_flow_ml(false);
4829 const ryml::Tree tree = ryml::parse_in_arena(yaml);
4830 CHECK(tree["map"].is_flow_ml1());
4831 CHECK(tree["map"]["seq"][4].is_flow_sl()); // [40,41] is FLOW_SL
4832 CHECK(tostr(tree, noindent) == ""
4833 "{" "\n"
4834 "map: {" "\n"
4835 "seq: [" "\n"
4836 "0," "\n"
4837 "1," "\n"
4838 "2," "\n"
4839 "3," "\n"
4840 "[40,41]" "\n"
4841 "]" "\n"
4842 "}" "\n"
4843 "}" "\n"
4844 "");
4845 CHECK(tostr_json(tree, noindent) == ""
4846 "{" "\n"
4847 "\"map\": {" "\n"
4848 "\"seq\": [" "\n"
4849 "0," "\n"
4850 "1," "\n"
4851 "2," "\n"
4852 "3," "\n"
4853 "[40,41]" "\n"
4854 "]" "\n"
4855 "}" "\n"
4856 "}" "\n"
4857 "");
4858 }
4859 // finally, you can control the number of columns in FLOW_MLN:
4860 {
4861 // let's pick a different example to make this clearer
4862 ryml::csubstr yaml2 = ""
4863 "[" "\n"
4864 " 0, 1, 2, 3, 4, 5, 6, 7, 8, 9," "\n"
4865 " 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, " "\n"
4866 " 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, " "\n"
4867 " 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, " "\n"
4868 " 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, " "\n"
4869 " 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, " "\n"
4870 " 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, " "\n"
4871 " 70, 71, 72, 73, 74, 75, 76, 77, 78, 79 " "\n"
4872 "]";
4873 // Let's force the parser to pick FLOW_MLN instead of
4874 // FLOW_ML1. We're doing that because wrapping is only done in
4875 // FLOW_MLN and -- as their names imply -- FLOW_SL is
4876 // single-line, and FLOW_ML1 is 1 value per line.
4879 const ryml::Tree tree = ryml::parse_in_arena(yaml2, opts);
4880 CHECK(tree.rootref().type().is_flow_mln());
4881 // default max columns is 80:
4882 CHECK(tostr(tree, emit_defaults) == ""
4883 "[\n"
4884 " 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,\n"
4885 " 30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,\n"
4886 " 56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79\n"
4887 "]\n"
4888 "");
4889 CHECK(tostr_json(tree, emit_defaults) == ""
4890 "[\n"
4891 " 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,\n"
4892 " 29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,\n"
4893 " 55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79\n"
4894 "]\n"
4895 "");
4896 // let's try setting max columns to 40:
4897 const ryml::EmitOptions maxcols40 = ryml::EmitOptions{}
4898 .max_cols(40);
4899 CHECK(tostr(tree, maxcols40) == ""
4900 "[\n"
4901 " 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,\n"
4902 " 16,17,18,19,20,21,22,23,24,25,26,27,28,\n"
4903 " 29,30,31,32,33,34,35,36,37,38,39,40,41,\n"
4904 " 42,43,44,45,46,47,48,49,50,51,52,53,54,\n"
4905 " 55,56,57,58,59,60,61,62,63,64,65,66,67,\n"
4906 " 68,69,70,71,72,73,74,75,76,77,78,79\n"
4907 "]\n"
4908 "");
4909 CHECK(tostr_json(tree, maxcols40) == ""
4910 "[\n"
4911 " 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,\n"
4912 " 16,17,18,19,20,21,22,23,24,25,26,27,28,\n"
4913 " 29,30,31,32,33,34,35,36,37,38,39,40,41,\n"
4914 " 42,43,44,45,46,47,48,49,50,51,52,53,54,\n"
4915 " 55,56,57,58,59,60,61,62,63,64,65,66,67,\n"
4916 " 68,69,70,71,72,73,74,75,76,77,78,79\n"
4917 "]\n"
4918 "");
4919 // Note that you can globally force spaces everywhere through
4920 // the emit options:
4921 const ryml::EmitOptions with_spaces = ryml::EmitOptions{}
4922 .force_flow_spc(true);
4923 CHECK(tostr(tree, with_spaces) == ""
4924 "[\n"
4925 " 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,\n"
4926 " 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41,\n"
4927 " 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,\n"
4928 " 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79\n"
4929 "]\n"
4930 "");
4931 CHECK(tostr_json(tree, with_spaces) == ""
4932 "[\n"
4933 " 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,\n"
4934 " 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41,\n"
4935 " 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,\n"
4936 " 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79\n"
4937 "]\n"
4938 "");
4939 // and you can combine spaces with max columns:
4940 const ryml::EmitOptions maxcols40_spc = ryml::EmitOptions{}
4941 .max_cols(40)
4942 .force_flow_spc(true);
4943 CHECK(tostr(tree, maxcols40_spc) == ""
4944 "[\n"
4945 " 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,\n"
4946 " 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,\n"
4947 " 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,\n"
4948 " 32, 33, 34, 35, 36, 37, 38, 39, 40, 41,\n"
4949 " 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,\n"
4950 " 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,\n"
4951 " 62, 63, 64, 65, 66, 67, 68, 69, 70, 71,\n"
4952 " 72, 73, 74, 75, 76, 77, 78, 79\n"
4953 "]\n"
4954 "");
4955 CHECK(tostr_json(tree, maxcols40_spc) == ""
4956 "[\n"
4957 " 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,\n"
4958 " 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,\n"
4959 " 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,\n"
4960 " 32, 33, 34, 35, 36, 37, 38, 39, 40, 41,\n"
4961 " 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,\n"
4962 " 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,\n"
4963 " 62, 63, 64, 65, 66, 67, 68, 69, 70, 71,\n"
4964 " 72, 73, 74, 75, 76, 77, 78, 79\n"
4965 "]\n"
4966 "");
4967 // and you can combine spaces with max columns with no indentation:
4968 const ryml::EmitOptions maxcols40_spc_noindent = ryml::EmitOptions{}
4969 .max_cols(40)
4970 .force_flow_spc(true)
4971 .indent_flow_ml(false);
4972 CHECK(tostr(tree, maxcols40_spc_noindent) == ""
4973 "[\n"
4974 "0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,\n"
4975 "13, 14, 15, 16, 17, 18, 19, 20, 21, 22,\n"
4976 "23, 24, 25, 26, 27, 28, 29, 30, 31, 32,\n"
4977 "33, 34, 35, 36, 37, 38, 39, 40, 41, 42,\n"
4978 "43, 44, 45, 46, 47, 48, 49, 50, 51, 52,\n"
4979 "53, 54, 55, 56, 57, 58, 59, 60, 61, 62,\n"
4980 "63, 64, 65, 66, 67, 68, 69, 70, 71, 72,\n"
4981 "73, 74, 75, 76, 77, 78, 79\n"
4982 "]\n"
4983 "");
4984 CHECK(tostr_json(tree, maxcols40_spc_noindent) == ""
4985 "[\n"
4986 "0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,\n"
4987 "13, 14, 15, 16, 17, 18, 19, 20, 21, 22,\n"
4988 "23, 24, 25, 26, 27, 28, 29, 30, 31, 32,\n"
4989 "33, 34, 35, 36, 37, 38, 39, 40, 41, 42,\n"
4990 "43, 44, 45, 46, 47, 48, 49, 50, 51, 52,\n"
4991 "53, 54, 55, 56, 57, 58, 59, 60, 61, 62,\n"
4992 "63, 64, 65, 66, 67, 68, 69, 70, 71, 72,\n"
4993 "73, 74, 75, 76, 77, 78, 79\n"
4994 "]\n"
4995 "");
4996 }
4997 // Note that FLOW_SPC is /not/ detected by the parser, and that
4998 // depending on the parse options, either FLOW_ML1 or FLOW_MLN
4999 // will be used for /all/ multiline flow containers.
5000}
substr emitrs_json(Tree const &t, id_type id, EmitOptions const &opts, CharOwningContainer *cont, bool append=false)
(1) emit+resize: emit JSON to the given std::string/std::vector<char>-like container,...
bool indent_flow_ml() const noexcept
Indent the contents of FLOW_ML1 and FLOW_MLN containers.
EmitOptions & force_flow_spc(bool enabled) noexcept
Force everywhere a space after comma in flow mode, overriding the FLOW_SPC status of individual conta...
bool is_flow_mln() const noexcept
ParserOptions & detect_flow_ml(bool enabled) noexcept
enable/disable detection of flow multiline container style.
ParserOptions & flow_ml_style(NodeType style) noexcept
choose the default style of multiline flow containers, when a container is detected as flow multiline...
NodeType type() const RYML_NOEXCEPT
Forward to Tree::type().
Definition node.hpp:194

◆ sample_style_flow_ml_indent()

void sample_style_flow_ml_indent ( )

control the indentation of emitted flow multiline containers

Definition at line 5006 of file quickstart.cpp.

5007{
5008 // we will be using this helper throughout this function
5009 auto tostr = [](ryml::ConstNodeRef n, ryml::EmitOptions opts) {
5010 return ryml::emitrs_yaml<std::string>(n, opts);
5011 };
5012 ryml::csubstr yaml = "{map: {seq: [0, 1, 2, 3, [40, 41]]}}";
5013 ryml::Tree tree = ryml::parse_in_arena(yaml);
5014 ryml::EmitOptions defaults = {};
5016 CHECK(tostr(tree, defaults) == "{map: {seq: [0,1,2,3,[40,41]]}}");
5017 // let's now set the style to FLOW_ML1 (it was FLOW_SL)
5020 tree["map"]["seq"].set_container_style(ryml::FLOW_ML1);
5021 tree["map"]["seq"][4].set_container_style(ryml::FLOW_ML1);
5022 // by default FLOW_ML1 prints one value per line, indented:
5023 CHECK(tostr(tree, defaults) ==
5024 "{" "\n"
5025 " map: {" "\n"
5026 " seq: [" "\n"
5027 " 0," "\n"
5028 " 1," "\n"
5029 " 2," "\n"
5030 " 3," "\n"
5031 " [" "\n"
5032 " 40," "\n"
5033 " 41" "\n"
5034 " ]" "\n"
5035 " ]" "\n"
5036 " }" "\n"
5037 "}" "\n"
5038 "");
5039 // if we use the noindent options, then each value is put at the
5040 // beginning of the line
5041 CHECK(tostr(tree, noindent) ==
5042 "{" "\n"
5043 "map: {" "\n"
5044 "seq: [" "\n"
5045 "0," "\n"
5046 "1," "\n"
5047 "2," "\n"
5048 "3," "\n"
5049 "[" "\n"
5050 "40," "\n"
5051 "41" "\n"
5052 "]" "\n"
5053 "]" "\n"
5054 "}" "\n"
5055 "}" "\n"
5056 "");
5057 // Note that the noindent option will safely respect any prior
5058 // indent level from enclosing block containers! For example:
5060 CHECK(tostr(tree, noindent) == ""// notice it is indented at the map level
5061 "map: {" "\n"
5062 " seq: [" "\n"
5063 " 0," "\n"
5064 " 1," "\n"
5065 " 2," "\n"
5066 " 3," "\n"
5067 " [" "\n"
5068 " 40," "\n"
5069 " 41" "\n"
5070 " ]" "\n"
5071 " ]" "\n"
5072 " }" "\n"
5073 "");
5074 // Let's set it one BLOCK level further:
5075 tree["map"].set_container_style(ryml::BLOCK);
5076 CHECK(tostr(tree, noindent) == // notice it is indented one more level
5077 "map:" "\n"
5078 " seq: [" "\n"
5079 " 0," "\n"
5080 " 1," "\n"
5081 " 2," "\n"
5082 " 3," "\n"
5083 " [" "\n"
5084 " 40," "\n"
5085 " 41" "\n"
5086 " ]" "\n"
5087 " ]" "\n"
5088 "");
5089}
void set_container_style(id_type node, NodeType_e style)
Definition tree.hpp:598

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

5099{
5100 ryml::csubstr json = ""
5101 "{" "\n"
5102 " \"doe\": \"a deer, a female deer\"," "\n"
5103 " \"ray\": \"a drop of golden sun\"," "\n"
5104 " \"me\": \"a name, I call myself\"," "\n"
5105 " \"far\": \"a long long way to go\"" "\n"
5106 "}" "\n"
5107 "";
5108 // Since JSON is a subset of YAML, parsing JSON is just the
5109 // same as YAML:
5110 ryml::Tree tree = ryml::parse_in_arena(json);
5111 // If you are sure the source is valid json, you can use the
5112 // appropriate parse_json overload, which is faster because json
5113 // has a smaller grammar:
5114 ryml::Tree json_tree = ryml::parse_json_in_arena(json);
5115 // to emit JSON:
5117 CHECK(ryml::emitrs_json<std::string>(json_tree) == json);
5118 // to emit JSON to a stream:
5119 std::stringstream ss;
5120 ss << ryml::as_json(tree); // <- mark it like this
5121 CHECK(ss.str() == json);
5122 // Note the following limitations:
5123 //
5124 // - YAML streams cannot be emitted as json, and are not
5125 // allowed. But you can work around this by emitting the
5126 // individual documents separately; see the sample_docs()
5127 // below for such an example.
5128 //
5129 // - tags cannot be emitted as json, and are not allowed.
5130 //
5131 // - anchors and references cannot be emitted as json and
5132 // are not allowed.
5133 //
5134
5135 // Note that when parsing JSON, ryml will the style of each node
5136 // in the JSON. This means that if you emit as YAML it will look
5137 // mostly the same as the JSON:
5138 CHECK(ryml::emitrs_yaml<std::string>(json_tree) == json);
5139 // If you want to avoid this, you will need to clear the style.
5140 json_tree.rootref().clear_style(); // clear the style of the map (without recursing)
5141 // note that this is now block mode. That is because when no
5142 // style is set, ryml defaults to emitting in block mode.
5143 CHECK(ryml::emitrs_yaml<std::string>(json_tree) == ""
5144 "\"doe\": \"a deer, a female deer\"" "\n"
5145 "\"ray\": \"a drop of golden sun\"" "\n"
5146 "\"me\": \"a name, I call myself\"" "\n"
5147 "\"far\": \"a long long way to go\"" "\n"
5148 "");
5149 // if you don't want the double quotes in the scalar, you can
5150 // recurse:
5151 json_tree.rootref().clear_style(/*recurse*/true);
5152 // so now when emitting you will get this:
5153 // (the scalars with a comma are single-quote)
5154 CHECK(ryml::emitrs_yaml<std::string>(json_tree) == ""
5155 "doe: a deer, a female deer" "\n"
5156 "ray: a drop of golden sun" "\n"
5157 "me: a name, I call myself" "\n"
5158 "far: a long long way to go" "\n"
5159 "");
5160 // you can do custom style changes based on a type mask. this
5161 // will change the style of all scalar values to single-quoted
5163 /*remflags*/ryml::VAL_STYLE,
5164 /*addflags*/ryml::VAL_SQUO,
5165 /*recurse*/true);
5167 "doe: 'a deer, a female deer'" "\n"
5168 "ray: 'a drop of golden sun'" "\n"
5169 "me: 'a name, I call myself'" "\n"
5170 "far: 'a long long way to go'" "\n"
5171 "");
5172 // see in particular sample_style() for more examples
5173}
void parse_json_in_arena(Parser *parser, csubstr filename, csubstr json, Tree *tree, 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:231

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

5199{
5200 std::string unresolved = ""
5201 "base: &base" "\n"
5202 " name: Everyone has same name" "\n"
5203 "foo: &foo" "\n"
5204 " <<: *base" "\n"
5205 " age: 10" "\n"
5206 "bar: &bar" "\n"
5207 " <<: *base" "\n"
5208 " age: 20" "\n"
5209 "bill_to: &id001" "\n"
5210 " street: |-" "\n"
5211 " 123 Tornado Alley" "\n"
5212 " Suite 16" "\n"
5213 " city: East Centerville" "\n"
5214 " state: KS" "\n"
5215 "ship_to: *id001" "\n"
5216 "&keyref key: &valref val" "\n"
5217 "*valref : *keyref" "\n"
5218 "";
5219 std::string resolved = ""
5220 "base:" "\n"
5221 " name: Everyone has same name" "\n"
5222 "foo:" "\n"
5223 " name: Everyone has same name" "\n"
5224 " age: 10" "\n"
5225 "bar:" "\n"
5226 " name: Everyone has same name" "\n"
5227 " age: 20" "\n"
5228 "bill_to:" "\n"
5229 " street: |-" "\n"
5230 " 123 Tornado Alley" "\n"
5231 " Suite 16" "\n"
5232 " city: East Centerville" "\n"
5233 " state: KS" "\n"
5234 "ship_to:" "\n"
5235 " street: |-" "\n"
5236 " 123 Tornado Alley" "\n"
5237 " Suite 16" "\n"
5238 " city: East Centerville" "\n"
5239 " state: KS" "\n"
5240 "key: val" "\n"
5241 "val: key" "\n"
5242 "";
5243
5245 // by default, references are not resolved when parsing:
5246 CHECK( ! tree["base"].has_key_anchor());
5247 CHECK( tree["base"].has_val_anchor());
5248 CHECK( tree["base"].val_anchor() == "base");
5249 CHECK( tree["key"].key_anchor() == "keyref");
5250 CHECK( tree["key"].val_anchor() == "valref");
5251 CHECK( tree["*valref"].is_key_ref());
5252 CHECK( tree["*valref"].is_val_ref());
5253 CHECK( tree["*valref"].key_ref() == "valref");
5254 CHECK( tree["*valref"].val_ref() == "keyref");
5255
5256 // to resolve references, simply call tree.resolve(),
5257 // which will perform the reference instantiations:
5258 tree.resolve();
5259
5260 // all the anchors and references are substistuted and then removed:
5261 CHECK( ! tree["base"].has_key_anchor());
5262 CHECK( ! tree["base"].has_val_anchor());
5263 CHECK( ! tree["base"].has_val_anchor());
5264 CHECK( ! tree["key"].has_key_anchor());
5265 CHECK( ! tree["key"].has_val_anchor());
5266 CHECK( ! tree["val"].is_key_ref()); // notice *valref is now turned to val
5267 CHECK( ! tree["val"].is_val_ref()); // notice *valref is now turned to val
5268
5269 CHECK(tree["ship_to"]["city"].val() == "East Centerville");
5270 CHECK(tree["ship_to"]["state"].val() == "KS");
5271}
void resolve(ReferenceResolver *rr, bool clear_anchors=true)
Resolve references (aliases <- anchors), by forwarding to ReferenceResolver::resolve(); refer to Refe...
Definition tree.cpp:1206

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

5276{
5277 // part 1: anchor/ref
5278 {
5279 ryml::Tree t;
5281 t["kanchor"].set_val("2");
5282 t["kanchor"].set_key_anchor("kanchor");
5283 t["vanchor"].set_val("3");
5284 t["vanchor"].set_val_anchor("vanchor");
5285 // to set a reference, need to call .set_val_ref()/.set_key_ref()
5286 t["kref"].set_val_ref("kanchor");
5287 t["vref"].set_val_ref("vanchor");
5288 t["nref"].set_val("*vanchor"); // NOTE: this is not set as a reference in the tree!
5290 "&kanchor kanchor: 2" "\n"
5291 "vanchor: &vanchor 3" "\n"
5292 "kref: *kanchor" "\n"
5293 "vref: *vanchor" "\n"
5294 // note that ryml emits nref with quotes to disambiguate
5295 // (because no style was set)
5296 "nref: '*vanchor'" "\n"
5297 "");
5298 t.resolve();
5300 "kanchor: 2" "\n"
5301 "vanchor: 3" "\n"
5302 "kref: kanchor" "\n"
5303 "vref: 3" "\n"
5304 // note that nref was not resolved
5305 "nref: '*vanchor'" "\n"
5306 "");
5307 }
5308
5309 // part 2: simple inheritance (ie, adding `<<: *anchor` nodes)
5310 {
5312 "orig: &orig {foo: bar, baz: bat}" "\n"
5313 "copy: {}" "\n"
5314 "notcopy: {}" "\n"
5315 "notref: {}" "\n"
5316 "");
5317 t["copy"]["<<"].set_val_ref("orig");
5318 t["notcopy"]["test"].set_val_ref("orig");
5319 t["notcopy"]["<<"].set_val_ref("orig");
5320 t["notref"]["<<"].set_val("*orig"); // not a reference! .set_val_ref() was not called
5322 "orig: &orig {foo: bar,baz: bat}" "\n"
5323 "copy: {<<: *orig}" "\n"
5324 "notcopy: {test: *orig,<<: *orig}" "\n"
5325 "notref: {<<: '*orig'}" "\n"
5326 "");
5327 t.resolve();
5329 "orig: {foo: bar,baz: bat}" "\n"
5330 "copy: {foo: bar,baz: bat}" "\n"
5331 "notcopy: {test: {foo: bar,baz: bat},foo: bar,baz: bat}" "\n"
5332 "notref: {<<: '*orig'}" "\n"
5333 "");
5334 }
5335
5336 // part 3: multiple inheritance (ie, `<<: [*ref1,*ref2,*etc]`)
5337 {
5339 "orig1: &orig1 {foo: bar}" "\n"
5340 "orig2: &orig2 {baz: bat}" "\n"
5341 "orig3: &orig3 {and: more}" "\n"
5342 "copy: {}" "\n"
5343 "");
5344 ryml::NodeRef seq = t["copy"]["<<"];
5345 seq.set_seq();
5346 seq.append_child().set_val_ref("orig1");
5347 seq.append_child().set_val_ref("orig2");
5348 seq.append_child().set_val_ref("orig3");
5350 "orig1: &orig1 {foo: bar}" "\n"
5351 "orig2: &orig2 {baz: bat}" "\n"
5352 "orig3: &orig3 {and: more}" "\n"
5353 "copy: {<<: [*orig1,*orig2,*orig3]}" "\n"
5354 "");
5355 t.resolve();
5357 "orig1: {foo: bar}" "\n"
5358 "orig2: {baz: bat}" "\n"
5359 "orig3: {and: more}" "\n"
5360 "copy: {foo: bar,baz: bat,and: more}" "\n");
5361 }
5362}
void set_val_ref(csubstr val_ref)
Definition node.hpp:970
void set_val_ref(id_type node, csubstr ref)
Definition tree.hpp:688
void set_val_anchor(id_type node, csubstr anchor)
Definition tree.hpp:686
void set_key_anchor(id_type node, csubstr anchor)
Definition tree.hpp:685

◆ sample_tags()

void sample_tags ( )

Definition at line 5367 of file quickstart.cpp.

5368{
5369 const std::string yaml = ""
5370 "--- !!map" "\n"
5371 "a: 0" "\n"
5372 "b: 1" "\n"
5373 "--- !map" "\n"
5374 "a: b" "\n"
5375 "--- !!seq" "\n"
5376 "- a" "\n"
5377 "- b" "\n"
5378 "--- !!str a b" "\n"
5379 "--- !!str 'a: b'" "\n"
5380 "---" "\n"
5381 "!!str a: b" "\n"
5382 "--- !!set" "\n"
5383 "? a" "\n"
5384 "? b" "\n"
5385 "--- !!set" "\n"
5386 "a:" "\n"
5387 "--- !!seq" "\n"
5388 "- !!int 0" "\n"
5389 "- !!str 1" "\n"
5390 "";
5392 const ryml::ConstNodeRef root = tree.rootref();
5393 CHECK(root.is_stream());
5394 CHECK(root.num_children() == 9);
5395 for(ryml::ConstNodeRef doc : root.children())
5396 CHECK(doc.is_doc());
5397 // tags are kept verbatim from the source:
5398 CHECK(root[0].has_val_tag());
5399 CHECK(root[0].val_tag() == "!!map"); // valid only if the node has a val tag
5400 CHECK(root[1].val_tag() == "!map");
5401 CHECK(root[2].val_tag() == "!!seq");
5402 CHECK(root[3].val_tag() == "!!str");
5403 CHECK(root[4].val_tag() == "!!str");
5404 CHECK(root[5]["a"].has_key_tag());
5405 CHECK(root[5]["a"].key_tag() == "!!str"); // valid only if the node has a key tag
5406 CHECK(root[6].val_tag() == "!!set");
5407 CHECK(root[7].val_tag() == "!!set");
5408 CHECK(root[8].val_tag() == "!!seq");
5409 CHECK(root[8][0].val_tag() == "!!int");
5410 CHECK(root[8][1].val_tag() == "!!str");
5411 // ryml also provides a complete toolbox to deal with tags.
5412 // there is an enumeration for the standard YAML tags:
5413 CHECK(ryml::to_tag("!map") == ryml::TAG_NONE);
5414 CHECK(ryml::to_tag("!!map") == ryml::TAG_MAP);
5415 CHECK(ryml::to_tag("!!seq") == ryml::TAG_SEQ);
5416 CHECK(ryml::to_tag("!!str") == ryml::TAG_STR);
5417 CHECK(ryml::to_tag("!!int") == ryml::TAG_INT);
5418 CHECK(ryml::to_tag("!!set") == ryml::TAG_SET);
5419 // given a tag enum, you can fetch the short tag string:
5421 CHECK(ryml::from_tag(ryml::TAG_MAP) == "!!map");
5422 CHECK(ryml::from_tag(ryml::TAG_SEQ) == "!!seq");
5423 CHECK(ryml::from_tag(ryml::TAG_STR) == "!!str");
5424 CHECK(ryml::from_tag(ryml::TAG_INT) == "!!int");
5425 CHECK(ryml::from_tag(ryml::TAG_SET) == "!!set");
5426 // you can also fetch the long tag string:
5428 CHECK(ryml::from_tag_long(ryml::TAG_MAP) == "<tag:yaml.org,2002:map>");
5429 CHECK(ryml::from_tag_long(ryml::TAG_SEQ) == "<tag:yaml.org,2002:seq>");
5430 CHECK(ryml::from_tag_long(ryml::TAG_STR) == "<tag:yaml.org,2002:str>");
5431 CHECK(ryml::from_tag_long(ryml::TAG_INT) == "<tag:yaml.org,2002:int>");
5432 CHECK(ryml::from_tag_long(ryml::TAG_SET) == "<tag:yaml.org,2002:set>");
5433 // and likewise:
5434 CHECK(ryml::to_tag("!map") == ryml::TAG_NONE);
5435 CHECK(ryml::to_tag("<tag:yaml.org,2002:map>") == ryml::TAG_MAP);
5436 CHECK(ryml::to_tag("<tag:yaml.org,2002:seq>") == ryml::TAG_SEQ);
5437 CHECK(ryml::to_tag("<tag:yaml.org,2002:str>") == ryml::TAG_STR);
5438 CHECK(ryml::to_tag("<tag:yaml.org,2002:int>") == ryml::TAG_INT);
5439 CHECK(ryml::to_tag("<tag:yaml.org,2002:set>") == ryml::TAG_SET);
5440 // to normalize a tag as much as possible, use normalize_tag():
5441 CHECK(ryml::normalize_tag("!!map") == "!!map");
5442 CHECK(ryml::normalize_tag("!<tag:yaml.org,2002:map>") == "!!map");
5443 CHECK(ryml::normalize_tag("<tag:yaml.org,2002:map>") == "!!map");
5444 CHECK(ryml::normalize_tag("tag:yaml.org,2002:map") == "!!map");
5445 CHECK(ryml::normalize_tag("!<!!map>") == "<!!map>");
5446 CHECK(ryml::normalize_tag("!map") == "!map");
5447 CHECK(ryml::normalize_tag("!my!foo") == "!my!foo");
5448 // and also for the long form:
5449 CHECK(ryml::normalize_tag_long("!!map") == "<tag:yaml.org,2002:map>");
5450 CHECK(ryml::normalize_tag_long("!<tag:yaml.org,2002:map>") == "<tag:yaml.org,2002:map>");
5451 CHECK(ryml::normalize_tag_long("<tag:yaml.org,2002:map>") == "<tag:yaml.org,2002:map>");
5452 CHECK(ryml::normalize_tag_long("tag:yaml.org,2002:map") == "<tag:yaml.org,2002:map>");
5453 CHECK(ryml::normalize_tag_long("!<!!map>") == "<!!map>");
5454 CHECK(ryml::normalize_tag_long("!map") == "!map");
5455 // The tree provides the following methods applying to every node
5456 // with a key and/or val tag:
5457 ryml::Tree normalized_tree = tree;
5458 normalized_tree.normalize_tags(); // normalize all tags in short form
5459 CHECK(ryml::emitrs_yaml<std::string>(normalized_tree) == ""
5460 "--- !!map" "\n"
5461 "a: 0" "\n"
5462 "b: 1" "\n"
5463 "--- !map" "\n"
5464 "a: b" "\n"
5465 "--- !!seq" "\n"
5466 "- a" "\n"
5467 "- b" "\n"
5468 "--- !!str a b" "\n"
5469 "--- !!str 'a: b'" "\n"
5470 "---" "\n"
5471 "!!str a: b" "\n"
5472 "--- !!set" "\n"
5473 "a: " "\n"
5474 "b: " "\n"
5475 "--- !!set" "\n"
5476 "a: " "\n"
5477 "--- !!seq" "\n"
5478 "- !!int 0" "\n"
5479 "- !!str 1" "\n"
5480 "");
5481 ryml::Tree normalized_tree_long = tree;
5482 normalized_tree_long.normalize_tags_long(); // normalize all tags in short form
5483 CHECK(ryml::emitrs_yaml<std::string>(normalized_tree_long) == ""
5484 "--- !<tag:yaml.org,2002:map>" "\n"
5485 "a: 0" "\n"
5486 "b: 1" "\n"
5487 "--- !map" "\n"
5488 "a: b" "\n"
5489 "--- !<tag:yaml.org,2002:seq>" "\n"
5490 "- a" "\n"
5491 "- b" "\n"
5492 "--- !<tag:yaml.org,2002:str> a b" "\n"
5493 "--- !<tag:yaml.org,2002:str> 'a: b'" "\n"
5494 "---" "\n"
5495 "!<tag:yaml.org,2002:str> a: b" "\n"
5496 "--- !<tag:yaml.org,2002:set>" "\n"
5497 "a: " "\n"
5498 "b: " "\n"
5499 "--- !<tag:yaml.org,2002:set>" "\n"
5500 "a: " "\n"
5501 "--- !<tag:yaml.org,2002:seq>" "\n"
5502 "- !<tag:yaml.org,2002:int> 0" "\n"
5503 "- !<tag:yaml.org,2002:str> 1" "\n"
5504 "");
5505}
void normalize_tags()
Definition tree.cpp:1575
void normalize_tags_long()
Definition tree.cpp:1582
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
!
Definition tag.hpp:39
@ TAG_INT
!
Definition tag.hpp:45
@ TAG_SEQ
!
Definition tag.hpp:40
@ TAG_NONE
Definition tag.hpp:34
@ TAG_STR
!
Definition tag.hpp:48
@ TAG_MAP
!
Definition tag.hpp:36
bool is_stream() const RYML_NOEXCEPT
Forward to Tree::is_stream().
Definition node.hpp:224

◆ sample_tag_directives()

void sample_tag_directives ( )

Definition at line 5510 of file quickstart.cpp.

5511{
5512 const std::string yaml = ""
5513 "%TAG !m! !my-" "\n"
5514 "--- # Bulb here" "\n"
5515 "!m!light fluorescent" "\n"
5516 "..." "\n"
5517 "%TAG !m! !meta-" "\n"
5518 "--- # Color here" "\n"
5519 "!m!light green" "\n"
5520 "";
5521 // tags are not resolved by default:
5524 "%TAG !m! !my-" "\n"
5525 "--- !m!light fluorescent" "\n"
5526 "..." "\n"
5527 "%TAG !m! !meta-" "\n"
5528 "--- !m!light green" "\n"
5529 "");
5530 // Use Tree::resolve_tags() to accomplish this in an existing
5531 // tree:
5532 ryml::TagCache tag_cache; // reduces memory requirements by reusing resolved tags
5533 tree.resolve_tags(tag_cache);
5535 "%TAG !m! !my-" "\n"
5536 "--- !<!my-light> fluorescent" "\n"
5537 "..." "\n"
5538 "%TAG !m! !meta-" "\n"
5539 "--- !<!meta-light> green" "\n"
5540 "");
5541 // You can also Use ParserOptions to force resolution of tags
5542 // while parsing:
5544 ryml::Tree resolved_tree = ryml::parse_in_arena(ryml::to_csubstr(yaml), opts);
5545 CHECK(ryml::emitrs_yaml<std::string>(resolved_tree) == ""
5546 "%TAG !m! !my-" "\n"
5547 "--- !<!my-light> fluorescent" "\n"
5548 "..." "\n"
5549 "%TAG !m! !meta-" "\n"
5550 "--- !<!meta-light> green" "\n"
5551 "");
5552 // see also tree.normalize_tags()
5553 // see also tree.normalize_tags_long()
5554}
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:1557
ParserOptions & resolve_tags(bool enabled) noexcept
enable/disable resolution of YAML tags during parsing.
Accelerator structure to reduce memory requirements by enabling reuse of resolved tags.
Definition tag.hpp:71

◆ sample_docs()

void sample_docs ( )

Definition at line 5559 of file quickstart.cpp.

5560{
5561 ryml::csubstr yml = ""
5562 "---" "\n"
5563 "a: 0" "\n"
5564 "b: 1" "\n"
5565 "---" "\n"
5566 "c: 2" "\n"
5567 "d: 3" "\n"
5568 "---" "\n"
5569 "- 4" "\n"
5570 "- 5" "\n"
5571 "- 6" "\n"
5572 "- 7" "\n"
5573 "";
5574 ryml::Tree tree = ryml::parse_in_arena(yml);
5576
5577 // iteration through docs
5578 {
5579 // using the node API
5580 const ryml::ConstNodeRef stream = tree.rootref();
5581 CHECK(stream.is_root());
5582 CHECK(stream.is_stream());
5583 CHECK(!stream.is_doc());
5584 CHECK(stream.num_children() == 3);
5585 for(const ryml::ConstNodeRef doc : stream.children())
5586 CHECK(doc.is_doc());
5587 CHECK(tree.docref(0).id() == stream.child(0).id());
5588 CHECK(tree.docref(1).id() == stream.child(1).id());
5589 CHECK(tree.docref(2).id() == stream.child(2).id());
5590 // equivalent: using the lower level index API
5591 const ryml::id_type stream_id = tree.root_id();
5592 CHECK(tree.is_root(stream_id));
5593 CHECK(tree.is_stream(stream_id));
5594 CHECK(!tree.is_doc(stream_id));
5595 CHECK(tree.num_children(stream_id) == 3);
5596 for(ryml::id_type doc_id = tree.first_child(stream_id);
5597 doc_id != ryml::NONE;
5598 doc_id = tree.next_sibling(stream_id))
5599 CHECK(tree.is_doc(doc_id));
5600 CHECK(tree.doc(0) == tree.child(stream_id, 0));
5601 CHECK(tree.doc(1) == tree.child(stream_id, 1));
5602 CHECK(tree.doc(2) == tree.child(stream_id, 2));
5603
5604 // using the node API
5605 CHECK(stream[0].is_doc());
5606 CHECK(stream[0].is_map());
5607 CHECK(stream[0]["a"].val() == "0");
5608 CHECK(stream[0]["b"].val() == "1");
5609 // equivalent: using the index API
5610 const ryml::id_type doc0_id = tree.first_child(stream_id);
5611 CHECK(tree.is_doc(doc0_id));
5612 CHECK(tree.is_map(doc0_id));
5613 CHECK(tree.val(tree.find_child(doc0_id, "a")) == "0");
5614 CHECK(tree.val(tree.find_child(doc0_id, "b")) == "1");
5615
5616 // using the node API
5617 CHECK(stream[1].is_doc());
5618 CHECK(stream[1].is_map());
5619 CHECK(stream[1]["c"].val() == "2");
5620 CHECK(stream[1]["d"].val() == "3");
5621 // equivalent: using the index API
5622 const ryml::id_type doc1_id = tree.next_sibling(doc0_id);
5623 CHECK(tree.is_doc(doc1_id));
5624 CHECK(tree.is_map(doc1_id));
5625 CHECK(tree.val(tree.find_child(doc1_id, "c")) == "2");
5626 CHECK(tree.val(tree.find_child(doc1_id, "d")) == "3");
5627
5628 // using the node API
5629 CHECK(stream[2].is_doc());
5630 CHECK(stream[2].is_seq());
5631 CHECK(stream[2][0].val() == "4");
5632 CHECK(stream[2][1].val() == "5");
5633 CHECK(stream[2][2].val() == "6");
5634 CHECK(stream[2][3].val() == "7");
5635 // equivalent: using the index API
5636 const ryml::id_type doc2_id = tree.next_sibling(doc1_id);
5637 CHECK(tree.is_doc(doc2_id));
5638 CHECK(tree.is_seq(doc2_id));
5639 CHECK(tree.val(tree.child(doc2_id, 0)) == "4");
5640 CHECK(tree.val(tree.child(doc2_id, 1)) == "5");
5641 CHECK(tree.val(tree.child(doc2_id, 2)) == "6");
5642 CHECK(tree.val(tree.child(doc2_id, 3)) == "7");
5643 }
5644
5645 // Note: ryml emits streams as a JSON seq by default:
5647 "[" "\n"
5648 " {" "\n"
5649 " \"a\": 0," "\n"
5650 " \"b\": 1" "\n"
5651 " }," "\n"
5652 " {" "\n"
5653 " \"c\": 2," "\n"
5654 " \"d\": 3" "\n"
5655 " }," "\n"
5656 " [" "\n"
5657 " 4," "\n"
5658 " 5," "\n"
5659 " 6," "\n"
5660 " 7" "\n"
5661 " ]" "\n"
5662 "]\n"
5663 "");
5664 // ... but you can use EmitOptions{} to set the emitter to fail if
5665 // it finds a stream:
5666 {
5667 ScopedErrorHandlerExample errh; // calls ryml::set_callbacks()
5668 ryml::Tree err_tree = ryml::parse_in_arena(yml);
5669 CHECK(err_tree.callbacks() == errh.callbacks());
5670 auto err_opts = ryml::EmitOptions{}.json_err_on_stream(true);
5671 CHECK(errh.check_error_occurs([&]{
5672 return ryml::emitrs_json<std::string>(err_tree, err_opts);
5673 }));
5674 // in which case, you can avoid the error by emitting the
5675 // documents one-by-one:
5676 const std::string expected_json[] = {
5677 "{" "\n"
5678 " \"a\": 0," "\n"
5679 " \"b\": 1" "\n"
5680 "}" "\n"
5681 ,
5682 "{" "\n"
5683 " \"c\": 2," "\n"
5684 " \"d\": 3" "\n"
5685 "}" "\n"
5686 ,
5687 "[" "\n"
5688 " 4," "\n"
5689 " 5," "\n"
5690 " 6," "\n"
5691 " 7" "\n"
5692 "]" "\n"
5693 };
5694 ryml::id_type count = 0;
5695 const ryml::ConstNodeRef stream = err_tree;
5696 CHECK(stream.num_children() == (ryml::id_type)C4_COUNTOF(expected_json));
5697 for(ryml::ConstNodeRef doc : stream.children())
5698 CHECK(ryml::emitrs_json<std::string>(doc, err_opts) == expected_json[count++]);
5699 }
5700}
ConstNodeRef child(id_type pos) const RYML_NOEXCEPT
Forward to Tree::child().
Definition node.hpp:598
id_type id() const noexcept
Definition node.hpp:917
bool is_stream(id_type node) const
Definition tree.hpp:425
bool is_root(id_type node) const
Definition tree.hpp:477
NodeRef docref(id_type i)
get the i-th document of the stream
Definition tree.cpp:112
bool is_doc(id_type node) const
Definition tree.hpp:426
id_type doc(id_type i) const
gets the i document node index.
Definition tree.hpp:545
id_type num_children(id_type node) const
O(num_children).
Definition tree.cpp:1216
id_type child(id_type node, id_type pos) const
Definition tree.cpp:1224
EmitOptions & json_err_on_stream(bool enabled) noexcept
Whether to trigger an error (or emit as seq) when finding a stream in json mode.
bool is_root() const RYML_NOEXCEPT
Forward to Tree::is_root().
Definition node.hpp:301
bool is_doc() const RYML_NOEXCEPT
Forward to Tree::is_doc().
Definition node.hpp:225

◆ sample_error_handler()

void sample_error_handler ( )

demonstrates how to set a custom error handler for ryml

Definition at line 5723 of file quickstart.cpp.

5724{
5725 ErrorHandlerExample errh; // browse the implementation of this
5726 // class to understand more details
5727 errh.check_disabled();
5728 // set the global error handlers. Note the error callbacks must
5729 // never return: they must either throw an exception, use setjmp()
5730 // and longjmp(), or abort. Otherwise, the parser will enter into
5731 // an infinite loop, or the program may crash.
5733 errh.check_enabled();
5734 CHECK(errh.check_error_occurs([&]{
5735 ryml::Tree tree = ryml::parse_in_arena("errorhandler.yml", "[a: b\n}");
5736 }));
5737 ryml::set_callbacks(errh.original_callbacks); // restore defaults.
5738 errh.check_disabled();
5739}
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.
ryml::Callbacks original_callbacks

◆ sample_error_basic()

void sample_error_basic ( )

Definition at line 5744 of file quickstart.cpp.

5745{
5746 auto cause_basic_error = []{
5747 ryml::Tree t;
5748 ryml::csubstr tag_handle = {}, tag_prefix = {}; // invalid, not filled
5749 t.add_tag_directive(tag_handle, tag_prefix, 0);
5750 };
5751 {
5752 ScopedErrorHandlerExample errh; // set the example callbacks (scoped)
5753 CHECK(errh.check_error_occurs(cause_basic_error));
5754 }
5755#ifdef _RYML_WITH_EXCEPTIONS
5756 bool gotit = false;
5757 try
5758 {
5759 cause_basic_error();
5760 }
5761 catch(ryml::ExceptionBasic const& exc)
5762 {
5763 gotit = true;
5764 ryml::csubstr msg = ryml::to_csubstr(exc.what());
5766 CHECK(!msg.empty());
5767 }
5768 CHECK(gotit);
5769#endif
5770}
void add_tag_directive(csubstr handle, csubstr prefix, id_type id)
Definition tree.cpp:1448
Location location
location where the error was detected (may be from YAML or C++ source code)
Definition common.hpp:377
Exception thrown by the default basic error implementation.
Definition error.hpp:478
const char * what() const noexcept override
Definition error.hpp:480
ErrorDataBasic errdata_basic
error data
Definition error.hpp:481
csubstr name
name of the file
Definition common.hpp:349

◆ sample_error_parse()

void sample_error_parse ( )

Definition at line 5773 of file quickstart.cpp.

5774{
5775 ryml::csubstr ymlsrc = ""
5776 "{" "\n"
5777 " a: b" "\n"
5778 " [" "\n"
5779 "";
5780 ryml::csubstr ymlfile = "file.yml";
5781 auto cause_parse_error = [&]{
5782 return ryml::parse_in_arena(ymlfile, ymlsrc);
5783 };
5784 // the YAML in ymlsrc must cause a parse error while it is being
5785 // parsed. We use our error handler to catch that error, and save
5786 // the error info:
5788 {
5790 CHECK(errh.check_error_occurs(cause_parse_error));
5791 // the handler in errh saves the error info in itself. Let's
5792 // use that to see the messages we get.
5793 //
5794 // this message is the short message passed into the parse
5795 // error handler:
5796 CHECK(errh.saved_msg_short == "invalid character: '['");
5797 // this message was created inside the handler, by calling
5798 // ryml::err_parse_format():
5799 CHECK(ryml::to_csubstr(errh.saved_msg_full).begins_with("file.yml:3: col=4 (12B): ERROR: [parse] invalid character: '['"));
5800 // If you keep the YAML source buffer around, you can also use
5801 // it to create/print a larger error message showing the
5802 // YAML source code context which causes the error:
5803 std::string msg_ctx = errh.saved_msg_full + "\n";
5805 msg_ctx.append(s.str, s.len);
5806 }, errh.saved_parse_loc, ymlsrc, "err");
5807 CHECK(ryml::to_csubstr(msg_ctx).begins_with("file.yml:3: col=4 (12B): ERROR: [parse] invalid character: '['"));
5808 CHECK(ryml::to_csubstr(msg_ctx).ends_with(
5809 "file.yml:3: col=4 (12B): err:" "\n"
5810 "err:" "\n"
5811 "err: [" "\n"
5812 "err: |" "\n"
5813 "err: (here)" "\n"
5814 "err:" "\n"
5815 "err: see region:" "\n"
5816 "err:" "\n"
5817 "err: {" "\n"
5818 "err: a: b" "\n"
5819 "err: [" "\n"
5820 "err: |" "\n"
5821 "err: (here)" "\n"
5822 ""));
5823 //
5824 // Let's now check the location (see the message above):
5825 CHECK(errh.saved_parse_loc.name == ymlfile);
5826 CHECK(errh.saved_parse_loc.line == 3);
5827 CHECK(errh.saved_parse_loc.col == 4);
5828 CHECK(errh.saved_parse_loc.offset == 12);
5829 CHECK(errh.saved_parse_loc.offset <= ymlsrc.len);
5830 // ... and this is the location in the ryml source code file where
5831 // this error was found:
5833 CHECK(errh.saved_basic_loc.line > 0);
5834 CHECK(errh.saved_basic_loc.col > 0);
5835 CHECK(errh.saved_basic_loc.offset > 0);
5837 }
5838 // A parse error is also a basic error. If no parse error handler
5839 // is set, then ryml falls back to a basic error:
5840 {
5841 ryml::Callbacks cb = errh.callbacks();
5842 cb.m_error_parse = nullptr;
5844 CHECK(ryml::get_callbacks().m_error_parse == nullptr);
5845 CHECK(errh.check_error_occurs(cause_parse_error));
5846 // we got a basic error instead of a parse error:
5847 CHECK(errh.saved_msg_short == "invalid character: '['");
5848 // notice that the full message now displays this as a basic
5849 // error:
5850 CHECK(errh.saved_msg_full == "file.yml:3: col=4 (12B): ERROR: [basic] invalid character: '['");
5851 // the yml location is now in the location saved from the basic error
5852 CHECK(errh.saved_basic_loc.name == ymlfile);
5853 CHECK(errh.saved_basic_loc.line == 3);
5854 CHECK(errh.saved_basic_loc.col == 4);
5855 CHECK(errh.saved_basic_loc.offset == 12);
5856 CHECK(errh.saved_basic_loc.offset <= ymlsrc.len);
5862 }
5863#ifdef _RYML_WITH_EXCEPTIONS
5864 bool gotit = false;
5865 try
5866 {
5867 cause_parse_error();
5868 }
5869 catch(ryml::ExceptionParse const& exc)
5870 {
5871 gotit = true;
5872 ryml::csubstr msg = ryml::to_csubstr(exc.what());
5873 CHECK(exc.errdata_parse.ymlloc.name == ymlfile);
5874 CHECK(exc.errdata_parse.ymlloc.line == 3);
5875 CHECK(exc.errdata_parse.ymlloc.col == 4);
5876 CHECK(exc.errdata_parse.ymlloc.offset == 12);
5877 CHECK(exc.errdata_parse.ymlloc.offset <= ymlsrc.len);
5878 // the message saved in the exception is just the concrete error description:
5879 CHECK(msg == "invalid character: '['");
5880 // to print richer error messages, ryml provides helpers to
5881 // format that description into a complete error message,
5882 // containing location and source context indication:
5883 std::string full;
5884 auto dumpfn = [&full](ryml::csubstr s) { full.append(s.str, s.len); };
5885 ryml::err_parse_format(dumpfn, msg, exc.errdata_parse);
5886 full += '\n';
5887 ryml::location_format_with_context(dumpfn, exc.errdata_parse.ymlloc, ymlsrc, "err", 3);
5888 CHECK(ryml::to_csubstr(full).begins_with("file.yml:3: col=4 (12B): ERROR: [parse] invalid character: '['"));
5889 CHECK(ryml::to_csubstr(full).ends_with(
5890 "file.yml:3: col=4 (12B): err:" "\n"
5891 "err:" "\n"
5892 "err: [" "\n"
5893 "err: |" "\n"
5894 "err: (here)" "\n"
5895 "err:" "\n"
5896 "err: see region:" "\n"
5897 "err:" "\n"
5898 "err: {" "\n"
5899 "err: a: b" "\n"
5900 "err: [" "\n"
5901 "err: |" "\n"
5902 "err: (here)" "\n"
5903 ""));
5904 }
5905 CHECK(gotit);
5906 gotit = false;
5907 try
5908 {
5909 cause_parse_error();
5910 }
5911 catch(ryml::ExceptionBasic const& exc) // use references! don't slice the exception
5912 {
5913 gotit = true;
5914 ryml::csubstr msg = ryml::to_csubstr(exc.what());
5916 CHECK(!msg.empty());
5917 }
5918 CHECK(gotit);
5919#endif
5920}
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.
ryml::Location saved_basic_loc
std::string saved_msg_full
std::string saved_msg_short
ryml::Location saved_parse_loc
A c-style callbacks class to customize behavior on errors or allocation.
Definition common.hpp:490
pfn_error_parse m_error_parse
a pointer to a parse error handler function
Definition common.hpp:495
Location ymlloc
location in the YAML source buffer where the error was detected.
Definition common.hpp:387
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:346

◆ sample_error_visit()

void sample_error_visit ( )

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

Definition at line 5925 of file quickstart.cpp.

5926{
5927 ryml::csubstr ymlfile = "file.yml";
5928 ryml::csubstr ymlsrc = "float: 123.456";
5930 {
5932 ryml::Tree tree = ryml::parse_in_arena(ymlfile, ymlsrc);
5933 CHECK(errh.check_error_occurs([&]{
5934 int intval = 0;
5935 tree["float"] >> intval; // cannot deserialize 123.456 to int
5936 }));
5937 // the handler in errh saves the error info in itself. Let's
5938 // use that to see the messages we get.
5939 //
5940 // this message is the short message passed into the visit error
5941 CHECK(errh.saved_msg_short == "could not deserialize value");
5942 // this message was created inside the handler, by calling
5943 // ryml::err_visit_format():
5944 CHECK(ryml::csubstr::npos != ryml::to_csubstr(errh.saved_msg_full).find("ERROR: [visit] could not deserialize value"));
5945 // The location of the visit error is of the C++ source file where
5946 // the error was detected -- NOT of the YAML source file:
5947 CHECK(errh.saved_basic_loc.name != ymlfile);
5948 // However, note that the tree and node id are available:
5949 CHECK(errh.saved_visit_tree == &tree);
5950 CHECK(errh.saved_visit_id == tree["float"].id());
5951 // see sample_error_visit_location() for an example on how
5952 // to extract the location.
5953 }
5954 // visit errors also fall back to basic errors when the visit
5955 // handler is not set (similar to the behavior of ExceptionVisit):
5956 {
5957 ryml::Callbacks cb = errh.callbacks();
5958 cb.m_error_visit = nullptr;
5960 CHECK(ryml::get_callbacks().m_error_visit == nullptr);
5961 ryml::Tree tree = ryml::parse_in_arena(ymlfile, ymlsrc);
5962 CHECK(errh.check_error_occurs([&]{
5963 int intval = 0;
5964 tree["float"] >> intval; // cannot deserialize 123.456 to int
5965 }));
5966 // we got a basic error instead of a visit error:
5967 CHECK(errh.saved_msg_short == "could not deserialize value");
5968 // notice that the full message now displays this as a basic
5969 // error:
5970 CHECK(ryml::csubstr::npos != ryml::to_csubstr(errh.saved_msg_full).find("ERROR: [basic] could not deserialize value"));
5971 // the tree and id are not set, because this was called as a basic error
5972 CHECK(errh.saved_visit_tree == nullptr);
5975 }
5976#ifdef _RYML_WITH_EXCEPTIONS
5977 // when using the default ryml callbacks (see
5978 // RYML_NO_DEFAULT_CALLBACKS), and
5979 // RYML_DEFAULT_CALLBACK_USES_EXCEPTIONS is defined, the ryml
5980 // parse handler throws an exception of type ryml::ExceptionVisit,
5981 // which is derived from ryml::ExceptionBasic.
5982 {
5983 const ryml::Tree tree = ryml::parse_in_arena(ymlfile, ymlsrc);
5984 bool gotit = false;
5985 try
5986 {
5987 int intval = 0;
5988 tree["float"] >> intval; // cannot deserialize 123.456 to int
5989 }
5990 catch(ryml::ExceptionVisit const& exc)
5991 {
5992 gotit = true;
5993 ryml::csubstr msg = ryml::to_csubstr(exc.what());
5995 CHECK(exc.errdata_visit.tree == &tree);
5996 CHECK(exc.errdata_visit.node == tree["float"].id());
5997 CHECK(!msg.empty());
5998 }
5999 CHECK(gotit);
6000 }
6001 // you can also catch the exception as its base,
6002 // ryml::ExceptionBasic:
6003 {
6004 const ryml::Tree tree = ryml::parse_in_arena(ymlfile, ymlsrc);
6005 bool gotit = false;
6006 try
6007 {
6008 int intval = 0;
6009 tree["float"] >> intval; // cannot deserialize 123.456 to int
6010 }
6011 catch(ryml::ExceptionBasic const& exc) // use references! don't slice the exception
6012 {
6013 gotit = true;
6014 ryml::csubstr msg = ryml::to_csubstr(exc.what());
6016 CHECK(!msg.empty());
6017 }
6018 CHECK(gotit);
6019 }
6020#endif
6021}
ryml::id_type saved_visit_id
ryml::Tree const * saved_visit_tree
pfn_error_visit m_error_visit
a pointer to a visit error handler function
Definition common.hpp:496
Location cpploc
location in the C++ source file where the error was detected.
Definition common.hpp:396
Tree const * tree
tree where the error was detected
Definition common.hpp:397
id_type node
node where the error was detected
Definition common.hpp:398
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 6029 of file quickstart.cpp.

6030{
6032 // we will use locations to show the YAML source context of the
6033 // node where the visit error was triggered. This is a very
6034 // convenient feature to show detailed messages when deserializing
6035 // data read from a file (but do note this is opt-in, and it is
6036 // not mandatory). See sample_location_tracking() for more details
6037 // on location tracking.
6039 ryml::EventHandlerTree evt_handler{};
6040 ryml::Parser parser(&evt_handler, opts);
6041 ryml::csubstr ymlfile = "file.yml";
6042 ryml::csubstr ymlsrc = ""
6043 "foo: bar" "\n"
6044 "char: a" "\n"
6045 "int: a" "\n"
6046 "float: 123.456" "\n"
6047 "";
6048 const ryml::Tree tree = ryml::parse_in_arena(&parser, ymlfile, ymlsrc);
6049 // This function will cause a visit error when being called:
6050 auto cause_visit_error = [&]{
6051 int intval = 0;
6052 tree["float"] >> intval; // cannot deserialize 123.456 to int
6053 };
6054 // Like with the parse error, we will use our error handler to
6055 // catch that visit error, and save the error info:
6056 CHECK(evt_handler.callbacks() == errh.callbacks());
6057 CHECK(parser.callbacks() == errh.callbacks());
6058 CHECK(tree.callbacks() == errh.callbacks());
6059 {
6060 CHECK(errh.check_error_occurs(cause_visit_error));
6061 // the handler in errh saves the error info in itself. Let's
6062 // use that to see the messages we get.
6063 //
6064 // this message is the short message passed into the visit error
6065 CHECK(errh.saved_msg_short == "could not deserialize value");
6066 // this message was created inside the handler, by calling
6067 // ryml::err_visit_format():
6068 CHECK(ryml::csubstr::npos != ryml::to_csubstr(errh.saved_msg_full).find("ERROR: [visit] could not deserialize value"));
6069 // The location of the visit error is of the C++ source file where
6070 // the error was detected -- NOT of the YAML source file:
6071 CHECK(errh.saved_basic_loc.name != ymlfile);
6072 // However, note that the tree and node id are available:
6073 CHECK(errh.saved_visit_tree == &tree);
6074 CHECK(errh.saved_visit_id == tree["float"].id());
6075 // ... which we can use to get the location in the YAML source
6076 // from the parser (but see @ref sample_location_tracking()):
6077 ryml::Location ymlloc = errh.saved_visit_tree->location(parser, errh.saved_visit_id);
6078 CHECK(ymlloc.name == ymlfile);
6079 // In turn, we can use format_location_context() to
6080 // print/create an error message pointing at the YAML source
6081 // code:
6082 std::string msg = errh.saved_msg_full;
6084 msg.append(s.str, s.len);
6085 }, ymlloc, ymlsrc, "err", /*number of lines to show before the error*/3);
6086 CHECK(ryml::to_csubstr(msg).ends_with(
6087 "file.yml:3: col=0 (24B): err:" "\n"
6088 "err:" "\n"
6089 "err: float: 123.456" "\n"
6090 "err: |" "\n"
6091 "err: (here)" "\n"
6092 "err:" "\n"
6093 "err: see region:" "\n"
6094 "err:" "\n"
6095 "err: foo: bar" "\n"
6096 "err: char: a" "\n"
6097 "err: int: a" "\n"
6098 "err: float: 123.456" "\n"
6099 "err: |" "\n"
6100 "err: (here)" "\n"
6101 ""));
6102 }
6103}
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:1913
Callbacks const & callbacks() const
ParserOptions & locations(bool enabled) noexcept
enable/disable source location tracking.

◆ sample_global_allocator()

void sample_global_allocator ( )

demonstrates how to set the global allocator for ryml

Definition at line 6203 of file quickstart.cpp.

6204{
6206
6207 // save the existing callbacks for restoring
6209
6210 // set to our callbacks
6212
6213 // verify that the allocator is in effect
6214 ryml::Callbacks const& current = ryml::get_callbacks();
6215 CHECK(current.m_allocate == &mem.s_allocate);
6216 CHECK(current.m_free == &mem.s_free);
6217
6218 // so far nothing was allocated
6219 CHECK(mem.alloc_size == 0);
6220
6221 // parse one tree and check
6222 (void)ryml::parse_in_arena(R"({foo: bar})");
6223 mem.check_and_reset();
6224
6225 // parse another tree and check
6226 (void)ryml::parse_in_arena(R"([a, b, c, d, {foo: bar, money: pennys}])");
6227 mem.check_and_reset();
6228
6229 // verify that by reserving we save allocations
6230 {
6231 ryml::EventHandlerTree evt_handler;
6232 ryml::Parser parser(&evt_handler); // reuse a parser
6233 ryml::Tree tree; // reuse a tree
6234
6235 tree.reserve(10); // reserve the number of nodes
6236 tree.reserve_arena(100); // reserve the arena size
6237 parser.reserve_stack(10); // reserve the parser depth.
6238
6239 // since the parser stack uses Small Storage Optimization,
6240 // allocations will only happen with capacities higher than 16.
6241 CHECK(mem.num_allocs == 2); // tree, tree_arena and NOT the parser
6242
6243 parser.reserve_stack(20); // reserve the parser depth.
6244 CHECK(mem.num_allocs == 3); // tree, tree_arena and now the parser as well
6245
6246 // verify that no other allocations occur when parsing
6247 size_t size_before = mem.alloc_size;
6248 parse_in_arena(&parser, "", R"([a, b, c, d, {foo: bar, money: pennys}])", &tree);
6249 CHECK(mem.alloc_size == size_before);
6250 CHECK(mem.num_allocs == 3);
6251 }
6252 mem.check_and_reset();
6253
6254 // restore defaults.
6255 ryml::set_callbacks(defaults);
6256}
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:492
pfn_free m_free
a pointer to a free handler function
Definition common.hpp:493

◆ sample_per_tree_allocator()

void sample_per_tree_allocator ( )

Definition at line 6326 of file quickstart.cpp.

6327{
6331
6332 // the trees will use the memory in the resources above,
6333 // with each tree using a separate resource
6334 {
6335 // Watchout: ensure that the lifetime of the callbacks target
6336 // exceeds the lifetime of the tree.
6337 ryml::EventHandlerTree evt_handler(mrp.callbacks());
6338 ryml::Parser parser(&evt_handler);
6339 ryml::Tree tree1(mr1.callbacks());
6340 ryml::Tree tree2(mr2.callbacks());
6341
6342 ryml::csubstr yml1 = "{a: b}";
6343 ryml::csubstr yml2 = "{c: d, e: f, g: [h, i, 0, 1, 2, 3]}";
6344
6345 parse_in_arena(&parser, "file1.yml", yml1, &tree1);
6346 parse_in_arena(&parser, "file2.yml", yml2, &tree2);
6347 }
6348
6349 CHECK(mrp.num_allocs == 0); // YAML depth not large enough to warrant a parser allocation
6350 CHECK(mr1.alloc_size <= mr2.alloc_size); // because yml2 has more nodes
6351}
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 6360 of file quickstart.cpp.

6361{
6362 // Static trees may incur a static initialization order
6363 // problem. This happens because a default-constructed tree will
6364 // obtain the callbacks from the current global setting, which may
6365 // not have been initialized due to undefined static
6366 // initialization order:
6367 //
6368 // ERROR! depends on ryml::get_callbacks() which may not have been initialized.
6369 //static ryml::Tree tree;
6370 //
6371 // To work around the issue, declare static callbacks
6372 // to explicitly initialize the static tree:
6373 static ryml::Callbacks callbacks = default_callbacks(); // use default callback members
6374 static ryml::Tree tree(callbacks); // OK
6375 // now you can use the tree as normal:
6376 ryml::parse_in_arena(R"(doe: "a deer, a female deer")", &tree);
6377 CHECK(tree["doe"].val() == "a deer, a female deer");
6378}
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 6386 of file quickstart.cpp.

6387{
6388 // NOTE: locations are zero-based. If you intend to show the
6389 // location to a human user, you may want to pre-increment the line
6390 // and column by 1.
6391 ryml::csubstr yaml = ""
6392 "{" "\n"
6393 "aa: contents," "\n"
6394 "foo: [one, [two, three]]" "\n"
6395 "}" "\n"
6396 "";
6397 // A parser is needed to track locations, and it has to be
6398 // explicitly set to do it. Location tracking is disabled by
6399 // default.
6400 ryml::ParserOptions opts = {};
6401 opts.locations(true); // enable locations, default is false
6402 ryml::EventHandlerTree evt_handler = {};
6403 ryml::Parser parser(&evt_handler, opts);
6404 CHECK(parser.options().locations());
6405 // When locations are enabled, the first task while parsing will
6406 // consist of building and caching (in the parser) a
6407 // source-to-node lookup structure to accelerate location lookups.
6408 //
6409 // The cost of building the location accelerator is linear in the
6410 // size of the source buffer. This increased cost is the reason
6411 // for the opt-in requirement. When locations are disabled there
6412 // is no cost.
6413 //
6414 // Building the location accelerator may trigger an allocation,
6415 // but this can and should be avoided by reserving prior to
6416 // parsing:
6417 parser.reserve_locations(50u); // reserve for 50 lines
6418 // Now the structure will be built during parsing:
6419 ryml::Tree tree = parse_in_arena(&parser, "source.yml", yaml);
6420 // After this, we are ready to query the location from the parser:
6421 ryml::Location loc = tree.rootref().location(parser);
6422 // As for the complexity of the query: for large buffers it is
6423 // O(log(numlines)). For short source buffers (30 lines and less),
6424 // it is O(numlines), as a plain linear search is faster in this
6425 // case.
6426 CHECK(parser.location_contents(loc).begins_with("{"));
6427 CHECK(loc.offset == 0u);
6428 CHECK(loc.line == 0u);
6429 CHECK(loc.col == 0u);
6430 // on the next call, we only pay O(log(numlines)) because the
6431 // rebuild is already available:
6432 loc = tree["aa"].location(parser);
6433 CHECK(parser.location_contents(loc).begins_with("aa"));
6434 CHECK(loc.offset == 2u);
6435 CHECK(loc.line == 1u);
6436 CHECK(loc.col == 0u);
6437 // KEYSEQ in flow style: points at the key
6438 loc = tree["foo"].location(parser);
6439 CHECK(parser.location_contents(loc).begins_with("foo"));
6440 CHECK(loc.offset == 16u);
6441 CHECK(loc.line == 2u);
6442 CHECK(loc.col == 0u);
6443 loc = tree["foo"][0].location(parser);
6444 CHECK(parser.location_contents(loc).begins_with("one"));
6445 CHECK(loc.line == 2u);
6446 CHECK(loc.col == 6u);
6447 // SEQ in flow style: location points at the opening '[' (there's no key)
6448 loc = tree["foo"][1].location(parser);
6449 CHECK(parser.location_contents(loc).begins_with("["));
6450 CHECK(loc.line == 2u);
6451 CHECK(loc.col == 11u);
6452 loc = tree["foo"][1][0].location(parser);
6453 CHECK(parser.location_contents(loc).begins_with("two"));
6454 CHECK(loc.line == 2u);
6455 CHECK(loc.col == 12u);
6456 loc = tree["foo"][1][1].location(parser);
6457 CHECK(parser.location_contents(loc).begins_with("three"));
6458 CHECK(loc.line == 2u);
6459 CHECK(loc.col == 17u);
6460 // NOTE. The parser locations always point at the latest buffer to
6461 // be parsed with the parser object, so they must be queried using
6462 // the corresponding latest tree to be parsed. This means that if
6463 // the parser is reused, earlier trees will loose the possibility
6464 // of querying for location. It is undefined behavior to query the
6465 // parser for the location of a node from an earlier tree:
6466 ryml::Tree docval = parse_in_arena(&parser, "docval.yaml", "this is a docval");
6467 // From now on, none of the locations from the previous tree can
6468 // be queried:
6469 //loc = tree.rootref().location(parser); // ERROR, undefined behavior
6470 loc = docval.rootref().location(parser); // OK. this is the latest tree from this parser
6471 CHECK(parser.location_contents(loc).begins_with("this is a docval"));
6472 CHECK(loc.line == 0u);
6473 CHECK(loc.col == 0u);
6474
6475 // NOTES ABOUT CONTAINER LOCATIONS
6476 ryml::Tree tree2 = parse_in_arena(&parser, "containers.yaml",
6477 "" "\n"
6478 "a new: buffer" "\n"
6479 "to: be parsed" "\n"
6480 "map with key:" "\n"
6481 " first: value" "\n"
6482 " second: value" "\n"
6483 "seq with key:" "\n"
6484 " - first value" "\n"
6485 " - second value" "\n"
6486 " -" "\n"
6487 " - nested first value" "\n"
6488 " - nested second value" "\n"
6489 " -" "\n"
6490 " nested first: value" "\n"
6491 " nested second: value" "\n"
6492 "");
6493 // (Likewise, the docval tree can no longer be used to query.)
6494 //
6495 // For key-less block-style maps, the location of the container
6496 // points at the first child's key. For example, in this case
6497 // the root does not have a key, so its location is taken
6498 // to be at the first child:
6499 loc = tree2.rootref().location(parser);
6500 CHECK(parser.location_contents(loc).begins_with("a new"));
6501 CHECK(loc.offset == 1u);
6502 CHECK(loc.line == 1u);
6503 CHECK(loc.col == 0u);
6504 // note the first child points exactly at the same place:
6505 loc = tree2["a new"].location(parser);
6506 CHECK(parser.location_contents(loc).begins_with("a new"));
6507 CHECK(loc.offset == 1u);
6508 CHECK(loc.line == 1u);
6509 CHECK(loc.col == 0u);
6510 loc = tree2["to"].location(parser);
6511 CHECK(parser.location_contents(loc).begins_with("to"));
6512 CHECK(loc.line == 2u);
6513 CHECK(loc.col == 0u);
6514 // but of course, if the block-style map is a KEYMAP, then the
6515 // location is the map's key, and not the first child's key:
6516 loc = tree2["map with key"].location(parser);
6517 CHECK(parser.location_contents(loc).begins_with("map with key"));
6518 CHECK(loc.line == 3u);
6519 CHECK(loc.col == 0u);
6520 loc = tree2["map with key"]["first"].location(parser);
6521 CHECK(parser.location_contents(loc).begins_with("first"));
6522 CHECK(loc.line == 4u);
6523 CHECK(loc.col == 2u);
6524 loc = tree2["map with key"]["second"].location(parser);
6525 CHECK(parser.location_contents(loc).begins_with("second"));
6526 CHECK(loc.line == 5u);
6527 CHECK(loc.col == 2u);
6528 // same thing for KEYSEQ:
6529 loc = tree2["seq with key"].location(parser);
6530 CHECK(parser.location_contents(loc).begins_with("seq with key"));
6531 CHECK(loc.line == 6u);
6532 CHECK(loc.col == 0u);
6533 loc = tree2["seq with key"][0].location(parser);
6534 CHECK(parser.location_contents(loc).begins_with("first value"));
6535 CHECK(loc.line == 7u);
6536 CHECK(loc.col == 4u);
6537 loc = tree2["seq with key"][1].location(parser);
6538 CHECK(parser.location_contents(loc).begins_with("second value"));
6539 CHECK(loc.line == 8u);
6540 CHECK(loc.col == 4u);
6541 // SEQ nested in SEQ: container location points at the first child's "- " dash
6542 loc = tree2["seq with key"][2].location(parser);
6543 CHECK(parser.location_contents(loc).begins_with("- nested first value"));
6544 CHECK(loc.line == 10u);
6545 CHECK(loc.col == 4u);
6546 loc = tree2["seq with key"][2][0].location(parser);
6547 CHECK(parser.location_contents(loc).begins_with("nested first value"));
6548 CHECK(loc.line == 10u);
6549 CHECK(loc.col == 6u);
6550 // MAP nested in SEQ: same as above: point to key
6551 loc = tree2["seq with key"][3].location(parser);
6552 CHECK(parser.location_contents(loc).begins_with("nested first: "));
6553 CHECK(loc.line == 13u);
6554 CHECK(loc.col == 4u);
6555 loc = tree2["seq with key"][3][0].location(parser);
6556 CHECK(parser.location_contents(loc).begins_with("nested first: "));
6557 CHECK(loc.line == 13u);
6558 CHECK(loc.col == 4u);
6559}