rapidyaml 0.15.1
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.

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

Now run the following commands in the same folder:

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

Function Documentation

◆ sample_lightning_overview()

void sample_lightning_overview ( )

a lightning tour over most features see sample_quick_overview

Definition at line 337 of file quickstart.cpp.

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

◆ sample_quick_overview()

void sample_quick_overview ( )

a brief tour over most features

Definition at line 391 of file quickstart.cpp.

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

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

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

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

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

◆ sample_parse_in_arena()

void sample_parse_in_arena ( )

demonstrate parsing of a read-only YAML source buffer

See also
Parse utilities

Definition at line 1933 of file quickstart.cpp.

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

◆ sample_parse_reuse_tree()

void sample_parse_reuse_tree ( )

demonstrate reuse/modification of tree when parsing

See also
Parse utilities

Definition at line 1979 of file quickstart.cpp.

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

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

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

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

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

◆ sample_iterate_trees()

void sample_iterate_trees ( )

shows how to programatically iterate through trees

See also
Tree utilities
Node classes

Definition at line 2237 of file quickstart.cpp.

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

◆ sample_create_trees()

void sample_create_trees ( )

shows how to programatically create trees

See also
Tree utilities
Node classes

Definition at line 2309 of file quickstart.cpp.

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

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

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

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

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

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

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

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

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

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

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

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

◆ sample_float_precision()

void sample_float_precision ( )

control precision of serialized floats

Definition at line 3902 of file quickstart.cpp.

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

◆ sample_emit_to_container()

void sample_emit_to_container ( )

demonstrates how to emit to a linear container of char

Definition at line 4039 of file quickstart.cpp.

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

◆ sample_emit_to_stream()

void sample_emit_to_stream ( )

demonstrates how to emit to a stream-like structure

Definition at line 4167 of file quickstart.cpp.

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

◆ sample_emit_to_file()

void sample_emit_to_file ( )

demonstrates how to emit to a FILE*

Definition at line 4264 of file quickstart.cpp.

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

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

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

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

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

◆ sample_style_flow_formatting()

void sample_style_flow_formatting ( )

Shows how to control formatting of flow styles.

Definition at line 4704 of file quickstart.cpp.

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

◆ sample_style_flow_ml_indent()

void sample_style_flow_ml_indent ( )

control the indentation of emitted flow multiline containers

Definition at line 5003 of file quickstart.cpp.

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

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

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

◆ sample_anchors_and_aliases()

void sample_anchors_and_aliases ( )

demonstrates usage with anchors and alias references.

Note that dereferencing is opt-in; after parsing, you have to call c4::yml::Tree::resolve() explicitly if you want resolved references in the tree. This method will resolve all references and substitute the anchored values in place of the reference.

The c4::yml::Tree::resolve() method first does a full traversal of the tree to gather all anchors and references in a separate collection, then it goes through that collection to locate the names, which it does by obeying the YAML standard diktat that

> an alias node refers to the most recent node in
> the serialization having the specified anchor

So, depending on the number of anchor/alias nodes, this is a potentially expensive operation, with a best-case linear complexity (from the initial traversal) and a worst-case quadratic complexity (if every node has an alias/anchor). This potential cost is the reason for requiring an explicit call to c4::yml::Tree::resolve()

Definition at line 5195 of file quickstart.cpp.

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

◆ sample_anchors_and_aliases_create()

void sample_anchors_and_aliases_create ( )

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

Definition at line 5272 of file quickstart.cpp.

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

◆ sample_tags()

void sample_tags ( )

Definition at line 5364 of file quickstart.cpp.

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

◆ sample_tag_directives()

void sample_tag_directives ( )

Definition at line 5507 of file quickstart.cpp.

5508{
5509 const std::string yaml = ""
5510 "%TAG !m! !my-" "\n"
5511 "--- # Bulb here" "\n"
5512 "!m!light fluorescent" "\n"
5513 "..." "\n"
5514 "%TAG !m! !meta-" "\n"
5515 "--- # Color here" "\n"
5516 "!m!light green" "\n"
5517 "";
5518 // tags are not resolved by default:
5521 "%TAG !m! !my-" "\n"
5522 "--- !m!light fluorescent" "\n"
5523 "..." "\n"
5524 "%TAG !m! !meta-" "\n"
5525 "--- !m!light green" "\n"
5526 "");
5527 // Use Tree::resolve_tags() to accomplish this in an existing
5528 // tree:
5529 ryml::TagCache tag_cache; // reduces memory requirements by reusing resolved tags
5530 tree.resolve_tags(tag_cache);
5532 "%TAG !m! !my-" "\n"
5533 "--- !<!my-light> fluorescent" "\n"
5534 "..." "\n"
5535 "%TAG !m! !meta-" "\n"
5536 "--- !<!meta-light> green" "\n"
5537 "");
5538 // You can also Use ParserOptions to force resolution of tags
5539 // while parsing:
5541 ryml::Tree resolved_tree = ryml::parse_in_arena(ryml::to_csubstr(yaml), opts);
5542 CHECK(ryml::emitrs_yaml<std::string>(resolved_tree) == ""
5543 "%TAG !m! !my-" "\n"
5544 "--- !<!my-light> fluorescent" "\n"
5545 "..." "\n"
5546 "%TAG !m! !meta-" "\n"
5547 "--- !<!meta-light> green" "\n"
5548 "");
5549 // see also tree.normalize_tags()
5550 // see also tree.normalize_tags_long()
5551}
void resolve_tags(TagCache &cache, bool all=true)
Resolve tags in the tree such as "!!str" -> "<tag:yaml.org,2002:str>", "!foo" -> "<!...
Definition tree.cpp:1555
ParserOptions & resolve_tags(bool enabled) noexcept
enable/disable resolution of YAML tags during parsing.
Accelerator structure to reduce memory requirements by enabling reuse of resolved tags.
Definition tag.hpp:71

◆ sample_docs()

void sample_docs ( )

Definition at line 5556 of file quickstart.cpp.

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

◆ sample_error_handler()

void sample_error_handler ( )

demonstrates how to set a custom error handler for ryml

Definition at line 5720 of file quickstart.cpp.

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

5742{
5743 auto cause_basic_error = []{
5744 ryml::Tree t;
5745 ryml::csubstr tag_handle = {}, tag_prefix = {}; // invalid, not filled
5746 t.add_tag_directive(tag_handle, tag_prefix, 0);
5747 };
5748 {
5749 ScopedErrorHandlerExample errh; // set the example callbacks (scoped)
5750 CHECK(errh.check_error_occurs(cause_basic_error));
5751 }
5752#ifdef _RYML_WITH_EXCEPTIONS
5753 bool gotit = false;
5754 try
5755 {
5756 cause_basic_error();
5757 }
5758 catch(ryml::ExceptionBasic const& exc)
5759 {
5760 gotit = true;
5761 ryml::csubstr msg = ryml::to_csubstr(exc.what());
5763 CHECK(!msg.empty());
5764 }
5765 CHECK(gotit);
5766#endif
5767}
void add_tag_directive(csubstr handle, csubstr prefix, id_type id)
Definition tree.cpp:1446
Location location
location where the error was detected (may be from YAML or C++ source code)
Definition common.hpp:328
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:300

◆ sample_error_parse()

void sample_error_parse ( )

Definition at line 5770 of file quickstart.cpp.

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

◆ sample_error_visit()

void sample_error_visit ( )

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

Definition at line 5922 of file quickstart.cpp.

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

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

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

◆ sample_per_tree_allocator()

void sample_per_tree_allocator ( )

Definition at line 6323 of file quickstart.cpp.

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

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

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