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

Now run the following commands in the same folder:

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

Function Documentation

◆ sample_lightning_overview()

void sample_lightning_overview ( )

a lightning tour over most features see sample_quick_overview

Definition at line 336 of file quickstart.cpp.

337{
338 // Parse YAML code in place, potentially mutating the buffer:
339 char yml_buf[] = "{foo: 1, bar: [2, 3], john: doe}";
340 ryml::Tree tree = ryml::parse_in_place(yml_buf);
341
342 // read from the tree:
343 ryml::NodeRef bar = tree["bar"];
344 CHECK(bar[0].val() == "2");
345 CHECK(bar[1].val() == "3");
346 CHECK(bar[0].val().str == yml_buf + 15); // points at the source buffer
347 CHECK(bar[1].val().str == yml_buf + 18);
348
349 // deserializing:
350 int bar0 = 0, bar1 = 0;
351 bar[0] >> bar0;
352 bar[1] >> bar1;
353 CHECK(bar0 == 2);
354 CHECK(bar1 == 3);
355
356 // serializing:
357 bar[0] << 10; // creates a string in the tree's arena
358 bar[1] << 11;
359 CHECK(bar[0].val() == "10");
360 CHECK(bar[1].val() == "11");
361
362 // add nodes
363 bar.append_child() << 12; // see also operator= (explanation below)
364 CHECK(bar[2].val() == "12");
365
366 // emit tree
367 std::string expected = "{foo: 1,bar: [10,11,12],john: doe}";
368 // emit tree to std::string
369 CHECK(ryml::emitrs_yaml<std::string>(tree) == expected);
370 // emit tree to FILE*
371 ryml::emit_yaml(tree, stdout); printf("\n");
372 // emit tree to ostream
373 std::cout << tree << "\n";
374
375 // emit node
376 ryml::ConstNodeRef foo = tree["foo"];
377 expected = "foo: 1\n";
378 // emit node to std::string
379 CHECK(ryml::emitrs_yaml<std::string>(foo) == expected);
380 // emit node to FILE*
381 ryml::emit_yaml(foo, stdout);
382 // emit node to ostream
383 std::cout << foo;
384}
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:871
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:522
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 390 of file quickstart.cpp.

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

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

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

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

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

◆ sample_parse_reuse_tree()

void sample_parse_reuse_tree ( )

demonstrate reuse/modification of tree when parsing

See also
Parse utilities

Definition at line 1978 of file quickstart.cpp.

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

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

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

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

◆ sample_iterate_trees()

void sample_iterate_trees ( )

shows how to programatically iterate through trees

See also
Tree utilities
Node classes

Definition at line 2236 of file quickstart.cpp.

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

◆ sample_create_trees()

void sample_create_trees ( )

shows how to programatically create trees

See also
Tree utilities
Node classes

Definition at line 2308 of file quickstart.cpp.

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

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

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

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

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

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

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

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

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

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

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

◆ sample_float_precision()

void sample_float_precision ( )

control precision of serialized floats

Definition at line 3901 of file quickstart.cpp.

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

◆ sample_emit_to_container()

void sample_emit_to_container ( )

demonstrates how to emit to a linear container of char

Definition at line 4038 of file quickstart.cpp.

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

◆ sample_emit_to_stream()

void sample_emit_to_stream ( )

demonstrates how to emit to a stream-like structure

Definition at line 4166 of file quickstart.cpp.

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

◆ sample_emit_to_file()

void sample_emit_to_file ( )

demonstrates how to emit to a FILE*

Definition at line 4263 of file quickstart.cpp.

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

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

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

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

4704{
4705 // we will be using this helper throughout this function
4706 auto tostr = [](ryml::ConstNodeRef n, ryml::EmitOptions opts) {
4707 return ryml::emitrs_yaml<std::string>(n, opts);
4708 };
4709 const ryml::EmitOptions emit_defaults = ryml::EmitOptions{};
4710 // let's parse this, which is in FLOW_ML1 (flow multiline, 1 value per line):
4711 ryml::csubstr yaml = ""
4712 "{" "\n"
4713 " map: {" "\n"
4714 " seq: [" "\n"
4715 " 0," "\n"
4716 " 1," "\n"
4717 " 2," "\n"
4718 " 3," "\n"
4719 " [40,41]" "\n"
4720 " ]" "\n"
4721 " }" "\n"
4722 "}" "\n"
4723 "";
4724 // note that the parser defaults to detecting multiline flow
4725 // (FLOW_ML1) containers:
4726 {
4727 const ryml::Tree tree = ryml::parse_in_arena(yaml);
4728 CHECK(tree["map"].is_flow_ml1()); // etc
4729 CHECK(tree["map"]["seq"].is_flow_ml1()); // etc
4730 CHECK(tree["map"]["seq"][4].is_flow_sl()); // etc
4731 // emitted yaml is exactly equal to parsed yaml:
4732 CHECK(tostr(tree, emit_defaults) == yaml);
4733 }
4734 // if you prefer to shorten the emitted yaml, you can set the
4735 // parser to disable flow multiline detection. It will then pick
4736 // singleline flow (FLOW_SL) for all flow containers:
4737 {
4739 .detect_flow_ml(false);
4740 const ryml::Tree tree = ryml::parse_in_arena(yaml, opts);
4741 CHECK(tree["map"].is_flow_sl()); // etc
4742 // notice how this is smaller now:
4743 CHECK(tostr(tree, emit_defaults) ==
4744 "{map: {seq: [0,1,2,3,[40,41]]}}");
4745 // you can also force spaces everywhere without adding
4746 // FLOW_SPC in individual containers:
4747 const ryml::EmitOptions with_spaces = ryml::EmitOptions{}
4748 .force_flow_spc(true);
4749 CHECK(tostr(tree, with_spaces) ==
4750 "{map: {seq: [0, 1, 2, 3, [40, 41]]}}");
4751 }
4752 // or you can still have the default detection of flow_ml, but set
4753 // it to pick FLOW_MLN (multiline, n values per line), instead of
4754 // the default FLOW_ML1 (multiline, 1 values per line)
4755 {
4758 const ryml::Tree tree = ryml::parse_in_arena(yaml, opts);
4759 CHECK(tree["map"].is_flow_mln());
4760 CHECK(tree["map"]["seq"][4].is_flow_sl()); // [40,41] is FLOW_SL
4761 CHECK(tostr(tree, emit_defaults) ==
4762 "{" "\n"
4763 " map: {" "\n"
4764 " seq: [" "\n"
4765 " 0,1,2,3,[40,41]" "\n"
4766 " ]" "\n"
4767 " }" "\n"
4768 "}" "\n");
4769 // now with spaces:
4770 const ryml::EmitOptions with_spaces = ryml::EmitOptions{}
4771 .force_flow_spc(true);
4772 CHECK(tostr(tree, with_spaces) ==
4773 "{" "\n"
4774 " map: {" "\n"
4775 " seq: [" "\n"
4776 " 0, 1, 2, 3, [40, 41]" "\n"
4777 " ]" "\n"
4778 " }" "\n"
4779 "}" "\n");
4780 }
4781 // you can also disable indentation of both FLOW_ML1 and FLOW_MLN
4782 // (see more details in @ref sample_style_flow_ml_indent())
4783 {
4784 const ryml::EmitOptions noindent = ryml::EmitOptions{}
4785 .indent_flow_ml(false);
4786 const ryml::Tree tree = ryml::parse_in_arena(yaml);
4787 CHECK(tree["map"].is_flow_ml1());
4788 CHECK(tree["map"]["seq"][4].is_flow_sl()); // [40,41] is FLOW_SL
4789 CHECK(tostr(tree, noindent) == ""
4790 "{" "\n"
4791 "map: {" "\n"
4792 "seq: [" "\n"
4793 "0," "\n"
4794 "1," "\n"
4795 "2," "\n"
4796 "3," "\n"
4797 "[40,41]" "\n"
4798 "]" "\n"
4799 "}" "\n"
4800 "}" "\n"
4801 "");
4802 }
4803 // finally, you can control the number of columns in FLOW_MLN:
4804 {
4805 // let's pick a different example to make this clearer
4806 ryml::csubstr yaml2 = ""
4807 "[" "\n"
4808 " 0, 1, 2, 3, 4, 5, 6, 7, 8, 9," "\n"
4809 " 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, " "\n"
4810 " 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, " "\n"
4811 " 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, " "\n"
4812 " 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, " "\n"
4813 " 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, " "\n"
4814 " 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, " "\n"
4815 " 70, 71, 72, 73, 74, 75, 76, 77, 78, 79 " "\n"
4816 "]";
4817 // Let's force the parser to pick FLOW_MLN instead of
4818 // FLOW_ML1. We're doing that because wrapping is only done in
4819 // FLOW_MLN and -- as their names imply -- FLOW_SL is
4820 // single-line, and FLOW_ML1 is 1 value per line.
4823 const ryml::Tree tree = ryml::parse_in_arena(yaml2, opts);
4824 CHECK(tree.rootref().type().is_flow_mln());
4825 // default max columns is 80:
4826 CHECK(tostr(tree, emit_defaults) == ""
4827 "[\n"
4828 " 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"
4829 " 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"
4830 " 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"
4831 "]\n"
4832 "");
4833 // let's try setting max columns to 40:
4834 const ryml::EmitOptions maxcols40 = ryml::EmitOptions{}
4835 .max_cols(40);
4836 CHECK(tostr(tree, maxcols40) == ""
4837 "[\n"
4838 " 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,\n"
4839 " 16,17,18,19,20,21,22,23,24,25,26,27,28,\n"
4840 " 29,30,31,32,33,34,35,36,37,38,39,40,41,\n"
4841 " 42,43,44,45,46,47,48,49,50,51,52,53,54,\n"
4842 " 55,56,57,58,59,60,61,62,63,64,65,66,67,\n"
4843 " 68,69,70,71,72,73,74,75,76,77,78,79\n"
4844 "]\n"
4845 "");
4846 // Note that you can globally force spaces everywhere through
4847 // the emit options:
4848 const ryml::EmitOptions with_spaces = ryml::EmitOptions{}
4849 .force_flow_spc(true);
4850 CHECK(tostr(tree, with_spaces) == ""
4851 "[\n"
4852 " 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,\n"
4853 " 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41,\n"
4854 " 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,\n"
4855 " 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79\n"
4856 "]\n"
4857 "");
4858 // and you can combine spaces with max columns:
4859 const ryml::EmitOptions maxcols40_spc = ryml::EmitOptions{}
4860 .max_cols(40)
4861 .force_flow_spc(true);
4862 CHECK(tostr(tree, maxcols40_spc) == ""
4863 "[\n"
4864 " 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,\n"
4865 " 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,\n"
4866 " 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,\n"
4867 " 32, 33, 34, 35, 36, 37, 38, 39, 40, 41,\n"
4868 " 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,\n"
4869 " 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,\n"
4870 " 62, 63, 64, 65, 66, 67, 68, 69, 70, 71,\n"
4871 " 72, 73, 74, 75, 76, 77, 78, 79\n"
4872 "]\n"
4873 "");
4874 // and you can combine spaces with max columns with no indentation:
4875 const ryml::EmitOptions maxcols40_spc_noindent = ryml::EmitOptions{}
4876 .max_cols(40)
4877 .force_flow_spc(true)
4878 .indent_flow_ml(false);
4879 CHECK(tostr(tree, maxcols40_spc_noindent) == ""
4880 "[\n"
4881 "0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,\n"
4882 "13, 14, 15, 16, 17, 18, 19, 20, 21, 22,\n"
4883 "23, 24, 25, 26, 27, 28, 29, 30, 31, 32,\n"
4884 "33, 34, 35, 36, 37, 38, 39, 40, 41, 42,\n"
4885 "43, 44, 45, 46, 47, 48, 49, 50, 51, 52,\n"
4886 "53, 54, 55, 56, 57, 58, 59, 60, 61, 62,\n"
4887 "63, 64, 65, 66, 67, 68, 69, 70, 71, 72,\n"
4888 "73, 74, 75, 76, 77, 78, 79\n"
4889 "]\n"
4890 "");
4891 }
4892 // Note that FLOW_SPC is /not/ detected by the parser, and that
4893 // depending on the parse options, either FLOW_ML1 or FLOW_MLN
4894 // will be used for /all/ multiline flow containers.
4895}
bool indent_flow_ml() const noexcept
Indent the contents of FLOW_ML1 and FLOW_MLN containers.
Definition emit.hpp:96
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:103
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 4901 of file quickstart.cpp.

4902{
4903 // we will be using this helper throughout this function
4904 auto tostr = [](ryml::ConstNodeRef n, ryml::EmitOptions opts) {
4905 return ryml::emitrs_yaml<std::string>(n, opts);
4906 };
4907 ryml::csubstr yaml = "{map: {seq: [0, 1, 2, 3, [40, 41]]}}";
4908 ryml::Tree tree = ryml::parse_in_arena(yaml);
4909 ryml::EmitOptions defaults = {};
4911 CHECK(tostr(tree, defaults) == "{map: {seq: [0,1,2,3,[40,41]]}}");
4912 // let's now set the style to FLOW_ML1 (it was FLOW_SL)
4915 tree["map"]["seq"].set_container_style(ryml::FLOW_ML1);
4916 tree["map"]["seq"][4].set_container_style(ryml::FLOW_ML1);
4917 // by default FLOW_ML1 prints one value per line, indented:
4918 CHECK(tostr(tree, defaults) ==
4919 "{" "\n"
4920 " map: {" "\n"
4921 " seq: [" "\n"
4922 " 0," "\n"
4923 " 1," "\n"
4924 " 2," "\n"
4925 " 3," "\n"
4926 " [" "\n"
4927 " 40," "\n"
4928 " 41" "\n"
4929 " ]" "\n"
4930 " ]" "\n"
4931 " }" "\n"
4932 "}" "\n"
4933 "");
4934 // if we use the noindent options, then each value is put at the
4935 // beginning of the line
4936 CHECK(tostr(tree, noindent) ==
4937 "{" "\n"
4938 "map: {" "\n"
4939 "seq: [" "\n"
4940 "0," "\n"
4941 "1," "\n"
4942 "2," "\n"
4943 "3," "\n"
4944 "[" "\n"
4945 "40," "\n"
4946 "41" "\n"
4947 "]" "\n"
4948 "]" "\n"
4949 "}" "\n"
4950 "}" "\n"
4951 "");
4952 // Note that the noindent option will safely respect any prior
4953 // indent level from enclosing block containers! For example:
4955 CHECK(tostr(tree, noindent) == ""// notice it is indented at the map level
4956 "map: {" "\n"
4957 " seq: [" "\n"
4958 " 0," "\n"
4959 " 1," "\n"
4960 " 2," "\n"
4961 " 3," "\n"
4962 " [" "\n"
4963 " 40," "\n"
4964 " 41" "\n"
4965 " ]" "\n"
4966 " ]" "\n"
4967 " }" "\n"
4968 "");
4969 // Let's set it one BLOCK level further:
4970 tree["map"].set_container_style(ryml::BLOCK);
4971 CHECK(tostr(tree, noindent) == // notice it is indented one more level
4972 "map:" "\n"
4973 " seq: [" "\n"
4974 " 0," "\n"
4975 " 1," "\n"
4976 " 2," "\n"
4977 " 3," "\n"
4978 " [" "\n"
4979 " 40," "\n"
4980 " 41" "\n"
4981 " ]" "\n"
4982 " ]" "\n"
4983 "");
4984}
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 4993 of file quickstart.cpp.

4994{
4995 ryml::csubstr json = ""
4996 "{" "\n"
4997 " \"doe\": \"a deer, a female deer\"," "\n"
4998 " \"ray\": \"a drop of golden sun\"," "\n"
4999 " \"me\": \"a name, I call myself\"," "\n"
5000 " \"far\": \"a long long way to go\"" "\n"
5001 "}" "\n"
5002 "";
5003 // Since JSON is a subset of YAML, parsing JSON is just the
5004 // same as YAML:
5005 ryml::Tree tree = ryml::parse_in_arena(json);
5006 // If you are sure the source is valid json, you can use the
5007 // appropriate parse_json overload, which is faster because json
5008 // has a smaller grammar:
5009 ryml::Tree json_tree = ryml::parse_json_in_arena(json);
5010 // to emit JSON:
5012 CHECK(ryml::emitrs_json<std::string>(json_tree) == json);
5013 // to emit JSON to a stream:
5014 std::stringstream ss;
5015 ss << ryml::as_json(tree); // <- mark it like this
5016 CHECK(ss.str() == json);
5017 // Note the following limitations:
5018 //
5019 // - YAML streams cannot be emitted as json, and are not
5020 // allowed. But you can work around this by emitting the
5021 // individual documents separately; see the sample_docs()
5022 // below for such an example.
5023 //
5024 // - tags cannot be emitted as json, and are not allowed.
5025 //
5026 // - anchors and references cannot be emitted as json and
5027 // are not allowed.
5028 //
5029
5030 // Note that when parsing JSON, ryml will the style of each node
5031 // in the JSON. This means that if you emit as YAML it will look
5032 // mostly the same as the JSON:
5033 CHECK(ryml::emitrs_yaml<std::string>(json_tree) == json);
5034 // If you want to avoid this, you will need to clear the style.
5035 json_tree.rootref().clear_style(); // clear the style of the map (without recursing)
5036 // note that this is now block mode. That is because when no
5037 // style is set, ryml defaults to emitting in block mode.
5038 CHECK(ryml::emitrs_yaml<std::string>(json_tree) == ""
5039 "\"doe\": \"a deer, a female deer\"" "\n"
5040 "\"ray\": \"a drop of golden sun\"" "\n"
5041 "\"me\": \"a name, I call myself\"" "\n"
5042 "\"far\": \"a long long way to go\"" "\n"
5043 "");
5044 // if you don't want the double quotes in the scalar, you can
5045 // recurse:
5046 json_tree.rootref().clear_style(/*recurse*/true);
5047 // so now when emitting you will get this:
5048 // (the scalars with a comma are single-quote)
5049 CHECK(ryml::emitrs_yaml<std::string>(json_tree) == ""
5050 "doe: a deer, a female deer" "\n"
5051 "ray: a drop of golden sun" "\n"
5052 "me: a name, I call myself" "\n"
5053 "far: a long long way to go" "\n"
5054 "");
5055 // you can do custom style changes based on a type mask. this
5056 // will change the style of all scalar values to single-quoted
5058 /*remflags*/ryml::VAL_STYLE,
5059 /*addflags*/ryml::VAL_SQUO,
5060 /*recurse*/true);
5062 "doe: 'a deer, a female deer'" "\n"
5063 "ray: 'a drop of golden sun'" "\n"
5064 "me: 'a name, I call myself'" "\n"
5065 "far: 'a long long way to go'" "\n"
5066 "");
5067 // see in particular sample_style() for more examples
5068}
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:901
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 5093 of file quickstart.cpp.

5094{
5095 std::string unresolved = ""
5096 "base: &base" "\n"
5097 " name: Everyone has same name" "\n"
5098 "foo: &foo" "\n"
5099 " <<: *base" "\n"
5100 " age: 10" "\n"
5101 "bar: &bar" "\n"
5102 " <<: *base" "\n"
5103 " age: 20" "\n"
5104 "bill_to: &id001" "\n"
5105 " street: |-" "\n"
5106 " 123 Tornado Alley" "\n"
5107 " Suite 16" "\n"
5108 " city: East Centerville" "\n"
5109 " state: KS" "\n"
5110 "ship_to: *id001" "\n"
5111 "&keyref key: &valref val" "\n"
5112 "*valref : *keyref" "\n"
5113 "";
5114 std::string resolved = ""
5115 "base:" "\n"
5116 " name: Everyone has same name" "\n"
5117 "foo:" "\n"
5118 " name: Everyone has same name" "\n"
5119 " age: 10" "\n"
5120 "bar:" "\n"
5121 " name: Everyone has same name" "\n"
5122 " age: 20" "\n"
5123 "bill_to:" "\n"
5124 " street: |-" "\n"
5125 " 123 Tornado Alley" "\n"
5126 " Suite 16" "\n"
5127 " city: East Centerville" "\n"
5128 " state: KS" "\n"
5129 "ship_to:" "\n"
5130 " street: |-" "\n"
5131 " 123 Tornado Alley" "\n"
5132 " Suite 16" "\n"
5133 " city: East Centerville" "\n"
5134 " state: KS" "\n"
5135 "key: val" "\n"
5136 "val: key" "\n"
5137 "";
5138
5140 // by default, references are not resolved when parsing:
5141 CHECK( ! tree["base"].has_key_anchor());
5142 CHECK( tree["base"].has_val_anchor());
5143 CHECK( tree["base"].val_anchor() == "base");
5144 CHECK( tree["key"].key_anchor() == "keyref");
5145 CHECK( tree["key"].val_anchor() == "valref");
5146 CHECK( tree["*valref"].is_key_ref());
5147 CHECK( tree["*valref"].is_val_ref());
5148 CHECK( tree["*valref"].key_ref() == "valref");
5149 CHECK( tree["*valref"].val_ref() == "keyref");
5150
5151 // to resolve references, simply call tree.resolve(),
5152 // which will perform the reference instantiations:
5153 tree.resolve();
5154
5155 // all the anchors and references are substistuted and then removed:
5156 CHECK( ! tree["base"].has_key_anchor());
5157 CHECK( ! tree["base"].has_val_anchor());
5158 CHECK( ! tree["base"].has_val_anchor());
5159 CHECK( ! tree["key"].has_key_anchor());
5160 CHECK( ! tree["key"].has_val_anchor());
5161 CHECK( ! tree["val"].is_key_ref()); // notice *valref is now turned to val
5162 CHECK( ! tree["val"].is_val_ref()); // notice *valref is now turned to val
5163
5164 CHECK(tree["ship_to"]["city"].val() == "East Centerville");
5165 CHECK(tree["ship_to"]["state"].val() == "KS");
5166}
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 5170 of file quickstart.cpp.

5171{
5172 // part 1: anchor/ref
5173 {
5174 ryml::Tree t;
5176 t["kanchor"] = "2";
5177 t["kanchor"].set_key_anchor("kanchor");
5178 t["vanchor"] = "3";
5179 t["vanchor"].set_val_anchor("vanchor");
5180 // to set a reference, need to call .set_val_ref()/.set_key_ref()
5181 t["kref"].set_val_ref("kanchor");
5182 t["vref"].set_val_ref("vanchor");
5183 t["nref"] = "*vanchor"; // NOTE: this is not set as a reference in the tree!
5185 "&kanchor kanchor: 2" "\n"
5186 "vanchor: &vanchor 3" "\n"
5187 "kref: *kanchor" "\n"
5188 "vref: *vanchor" "\n"
5189 // note that ryml emits nref with quotes to disambiguate
5190 // (because no style was set)
5191 "nref: '*vanchor'" "\n"
5192 "");
5193 t.resolve();
5195 "kanchor: 2" "\n"
5196 "vanchor: 3" "\n"
5197 "kref: kanchor" "\n"
5198 "vref: 3" "\n"
5199 // note that nref was not resolved
5200 "nref: '*vanchor'" "\n"
5201 "");
5202 }
5203
5204 // part 2: simple inheritance (ie, adding `<<: *anchor` nodes)
5205 {
5207 "orig: &orig {foo: bar, baz: bat}" "\n"
5208 "copy: {}" "\n"
5209 "notcopy: {}" "\n"
5210 "notref: {}" "\n"
5211 "");
5212 t["copy"]["<<"].set_val_ref("orig");
5213 t["notcopy"]["test"].set_val_ref("orig");
5214 t["notcopy"]["<<"].set_val_ref("orig");
5215 t["notref"]["<<"] = "*orig"; // not a reference! .set_val_ref() was not called
5217 "orig: &orig {foo: bar,baz: bat}" "\n"
5218 "copy: {<<: *orig}" "\n"
5219 "notcopy: {test: *orig,<<: *orig}" "\n"
5220 "notref: {<<: '*orig'}" "\n"
5221 "");
5222 t.resolve();
5224 "orig: {foo: bar,baz: bat}" "\n"
5225 "copy: {foo: bar,baz: bat}" "\n"
5226 "notcopy: {test: {foo: bar,baz: bat},foo: bar,baz: bat}" "\n"
5227 "notref: {<<: '*orig'}" "\n"
5228 "");
5229 }
5230
5231 // part 3: multiple inheritance (ie, `<<: [*ref1,*ref2,*etc]`)
5232 {
5234 "orig1: &orig1 {foo: bar}" "\n"
5235 "orig2: &orig2 {baz: bat}" "\n"
5236 "orig3: &orig3 {and: more}" "\n"
5237 "copy: {}" "\n"
5238 "");
5239 ryml::NodeRef seq = t["copy"]["<<"];
5240 seq |= ryml::SEQ;
5241 seq.append_child().set_val_ref("orig1");
5242 seq.append_child().set_val_ref("orig2");
5243 seq.append_child().set_val_ref("orig3");
5245 "orig1: &orig1 {foo: bar}" "\n"
5246 "orig2: &orig2 {baz: bat}" "\n"
5247 "orig3: &orig3 {and: more}" "\n"
5248 "copy: {<<: [*orig1,*orig2,*orig3]}" "\n"
5249 "");
5250 t.resolve();
5252 "orig1: {foo: bar}" "\n"
5253 "orig2: {baz: bat}" "\n"
5254 "orig3: {and: more}" "\n"
5255 "copy: {foo: bar,baz: bat,and: more}" "\n");
5256 }
5257}
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 5262 of file quickstart.cpp.

5263{
5264 const std::string yaml = ""
5265 "--- !!map" "\n"
5266 "a: 0" "\n"
5267 "b: 1" "\n"
5268 "--- !map" "\n"
5269 "a: b" "\n"
5270 "--- !!seq" "\n"
5271 "- a" "\n"
5272 "- b" "\n"
5273 "--- !!str a b" "\n"
5274 "--- !!str 'a: b'" "\n"
5275 "---" "\n"
5276 "!!str a: b" "\n"
5277 "--- !!set" "\n"
5278 "? a" "\n"
5279 "? b" "\n"
5280 "--- !!set" "\n"
5281 "a:" "\n"
5282 "--- !!seq" "\n"
5283 "- !!int 0" "\n"
5284 "- !!str 1" "\n"
5285 "";
5287 const ryml::ConstNodeRef root = tree.rootref();
5288 CHECK(root.is_stream());
5289 CHECK(root.num_children() == 9);
5290 for(ryml::ConstNodeRef doc : root.children())
5291 CHECK(doc.is_doc());
5292 // tags are kept verbatim from the source:
5293 CHECK(root[0].has_val_tag());
5294 CHECK(root[0].val_tag() == "!!map"); // valid only if the node has a val tag
5295 CHECK(root[1].val_tag() == "!map");
5296 CHECK(root[2].val_tag() == "!!seq");
5297 CHECK(root[3].val_tag() == "!!str");
5298 CHECK(root[4].val_tag() == "!!str");
5299 CHECK(root[5]["a"].has_key_tag());
5300 CHECK(root[5]["a"].key_tag() == "!!str"); // valid only if the node has a key tag
5301 CHECK(root[6].val_tag() == "!!set");
5302 CHECK(root[7].val_tag() == "!!set");
5303 CHECK(root[8].val_tag() == "!!seq");
5304 CHECK(root[8][0].val_tag() == "!!int");
5305 CHECK(root[8][1].val_tag() == "!!str");
5306 // ryml also provides a complete toolbox to deal with tags.
5307 // there is an enumeration for the standard YAML tags:
5308 CHECK(ryml::to_tag("!map") == ryml::TAG_NONE);
5309 CHECK(ryml::to_tag("!!map") == ryml::TAG_MAP);
5310 CHECK(ryml::to_tag("!!seq") == ryml::TAG_SEQ);
5311 CHECK(ryml::to_tag("!!str") == ryml::TAG_STR);
5312 CHECK(ryml::to_tag("!!int") == ryml::TAG_INT);
5313 CHECK(ryml::to_tag("!!set") == ryml::TAG_SET);
5314 // given a tag enum, you can fetch the short tag string:
5316 CHECK(ryml::from_tag(ryml::TAG_MAP) == "!!map");
5317 CHECK(ryml::from_tag(ryml::TAG_SEQ) == "!!seq");
5318 CHECK(ryml::from_tag(ryml::TAG_STR) == "!!str");
5319 CHECK(ryml::from_tag(ryml::TAG_INT) == "!!int");
5320 CHECK(ryml::from_tag(ryml::TAG_SET) == "!!set");
5321 // you can also fetch the long tag string:
5323 CHECK(ryml::from_tag_long(ryml::TAG_MAP) == "<tag:yaml.org,2002:map>");
5324 CHECK(ryml::from_tag_long(ryml::TAG_SEQ) == "<tag:yaml.org,2002:seq>");
5325 CHECK(ryml::from_tag_long(ryml::TAG_STR) == "<tag:yaml.org,2002:str>");
5326 CHECK(ryml::from_tag_long(ryml::TAG_INT) == "<tag:yaml.org,2002:int>");
5327 CHECK(ryml::from_tag_long(ryml::TAG_SET) == "<tag:yaml.org,2002:set>");
5328 // and likewise:
5329 CHECK(ryml::to_tag("!map") == ryml::TAG_NONE);
5330 CHECK(ryml::to_tag("<tag:yaml.org,2002:map>") == ryml::TAG_MAP);
5331 CHECK(ryml::to_tag("<tag:yaml.org,2002:seq>") == ryml::TAG_SEQ);
5332 CHECK(ryml::to_tag("<tag:yaml.org,2002:str>") == ryml::TAG_STR);
5333 CHECK(ryml::to_tag("<tag:yaml.org,2002:int>") == ryml::TAG_INT);
5334 CHECK(ryml::to_tag("<tag:yaml.org,2002:set>") == ryml::TAG_SET);
5335 // to normalize a tag as much as possible, use normalize_tag():
5336 CHECK(ryml::normalize_tag("!!map") == "!!map");
5337 CHECK(ryml::normalize_tag("!<tag:yaml.org,2002:map>") == "!!map");
5338 CHECK(ryml::normalize_tag("<tag:yaml.org,2002:map>") == "!!map");
5339 CHECK(ryml::normalize_tag("tag:yaml.org,2002:map") == "!!map");
5340 CHECK(ryml::normalize_tag("!<!!map>") == "<!!map>");
5341 CHECK(ryml::normalize_tag("!map") == "!map");
5342 CHECK(ryml::normalize_tag("!my!foo") == "!my!foo");
5343 // and also for the long form:
5344 CHECK(ryml::normalize_tag_long("!!map") == "<tag:yaml.org,2002:map>");
5345 CHECK(ryml::normalize_tag_long("!<tag:yaml.org,2002:map>") == "<tag:yaml.org,2002:map>");
5346 CHECK(ryml::normalize_tag_long("<tag:yaml.org,2002:map>") == "<tag:yaml.org,2002:map>");
5347 CHECK(ryml::normalize_tag_long("tag:yaml.org,2002:map") == "<tag:yaml.org,2002:map>");
5348 CHECK(ryml::normalize_tag_long("!<!!map>") == "<!!map>");
5349 CHECK(ryml::normalize_tag_long("!map") == "!map");
5350 // The tree provides the following methods applying to every node
5351 // with a key and/or val tag:
5352 ryml::Tree normalized_tree = tree;
5353 normalized_tree.normalize_tags(); // normalize all tags in short form
5354 CHECK(ryml::emitrs_yaml<std::string>(normalized_tree) == ""
5355 "--- !!map" "\n"
5356 "a: 0" "\n"
5357 "b: 1" "\n"
5358 "--- !map" "\n"
5359 "a: b" "\n"
5360 "--- !!seq" "\n"
5361 "- a" "\n"
5362 "- b" "\n"
5363 "--- !!str a b" "\n"
5364 "--- !!str 'a: b'" "\n"
5365 "---" "\n"
5366 "!!str a: b" "\n"
5367 "--- !!set" "\n"
5368 "a: " "\n"
5369 "b: " "\n"
5370 "--- !!set" "\n"
5371 "a: " "\n"
5372 "--- !!seq" "\n"
5373 "- !!int 0" "\n"
5374 "- !!str 1" "\n"
5375 "");
5376 ryml::Tree normalized_tree_long = tree;
5377 normalized_tree_long.normalize_tags_long(); // normalize all tags in short form
5378 CHECK(ryml::emitrs_yaml<std::string>(normalized_tree_long) == ""
5379 "--- !<tag:yaml.org,2002:map>" "\n"
5380 "a: 0" "\n"
5381 "b: 1" "\n"
5382 "--- !map" "\n"
5383 "a: b" "\n"
5384 "--- !<tag:yaml.org,2002:seq>" "\n"
5385 "- a" "\n"
5386 "- b" "\n"
5387 "--- !<tag:yaml.org,2002:str> a b" "\n"
5388 "--- !<tag:yaml.org,2002:str> 'a: b'" "\n"
5389 "---" "\n"
5390 "!<tag:yaml.org,2002:str> a: b" "\n"
5391 "--- !<tag:yaml.org,2002:set>" "\n"
5392 "a: " "\n"
5393 "b: " "\n"
5394 "--- !<tag:yaml.org,2002:set>" "\n"
5395 "a: " "\n"
5396 "--- !<tag:yaml.org,2002:seq>" "\n"
5397 "- !<tag:yaml.org,2002:int> 0" "\n"
5398 "- !<tag:yaml.org,2002:str> 1" "\n"
5399 "");
5400}
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 5405 of file quickstart.cpp.

5406{
5407 const std::string yaml = ""
5408 "%TAG !m! !my-" "\n"
5409 "--- # Bulb here" "\n"
5410 "!m!light fluorescent" "\n"
5411 "..." "\n"
5412 "%TAG !m! !meta-" "\n"
5413 "--- # Color here" "\n"
5414 "!m!light green" "\n"
5415 "";
5416 // tags are not resolved by default:
5419 "%TAG !m! !my-" "\n"
5420 "--- !m!light fluorescent" "\n"
5421 "..." "\n"
5422 "%TAG !m! !meta-" "\n"
5423 "--- !m!light green" "\n"
5424 "");
5425 // Use Tree::resolve_tags() to accomplish this in an existing
5426 // tree:
5427 ryml::TagCache tag_cache; // reduces memory requirements by reusing resolved tags
5428 tree.resolve_tags(tag_cache);
5430 "%TAG !m! !my-" "\n"
5431 "--- !<!my-light> fluorescent" "\n"
5432 "..." "\n"
5433 "%TAG !m! !meta-" "\n"
5434 "--- !<!meta-light> green" "\n"
5435 "");
5436 // You can also Use ParserOptions to force resolution of tags
5437 // while parsing:
5439 ryml::Tree resolved_tree = ryml::parse_in_arena(ryml::to_csubstr(yaml), opts);
5440 CHECK(ryml::emitrs_yaml<std::string>(resolved_tree) == ""
5441 "%TAG !m! !my-" "\n"
5442 "--- !<!my-light> fluorescent" "\n"
5443 "..." "\n"
5444 "%TAG !m! !meta-" "\n"
5445 "--- !<!meta-light> green" "\n"
5446 "");
5447 // see also tree.normalize_tags()
5448 // see also tree.normalize_tags_long()
5449}
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 5454 of file quickstart.cpp.

5455{
5456 std::string yml = ""
5457 "---" "\n"
5458 "a: 0" "\n"
5459 "b: 1" "\n"
5460 "---" "\n"
5461 "c: 2" "\n"
5462 "d: 3" "\n"
5463 "---" "\n"
5464 "- 4" "\n"
5465 "- 5" "\n"
5466 "- 6" "\n"
5467 "- 7" "\n"
5468 "";
5471
5472 // iteration through docs
5473 {
5474 // using the node API
5475 const ryml::ConstNodeRef stream = tree.rootref();
5476 CHECK(stream.is_root());
5477 CHECK(stream.is_stream());
5478 CHECK(!stream.is_doc());
5479 CHECK(stream.num_children() == 3);
5480 for(const ryml::ConstNodeRef doc : stream.children())
5481 CHECK(doc.is_doc());
5482 CHECK(tree.docref(0).id() == stream.child(0).id());
5483 CHECK(tree.docref(1).id() == stream.child(1).id());
5484 CHECK(tree.docref(2).id() == stream.child(2).id());
5485 // equivalent: using the lower level index API
5486 const ryml::id_type stream_id = tree.root_id();
5487 CHECK(tree.is_root(stream_id));
5488 CHECK(tree.is_stream(stream_id));
5489 CHECK(!tree.is_doc(stream_id));
5490 CHECK(tree.num_children(stream_id) == 3);
5491 for(ryml::id_type doc_id = tree.first_child(stream_id);
5492 doc_id != ryml::NONE;
5493 doc_id = tree.next_sibling(stream_id))
5494 CHECK(tree.is_doc(doc_id));
5495 CHECK(tree.doc(0) == tree.child(stream_id, 0));
5496 CHECK(tree.doc(1) == tree.child(stream_id, 1));
5497 CHECK(tree.doc(2) == tree.child(stream_id, 2));
5498
5499 // using the node API
5500 CHECK(stream[0].is_doc());
5501 CHECK(stream[0].is_map());
5502 CHECK(stream[0]["a"].val() == "0");
5503 CHECK(stream[0]["b"].val() == "1");
5504 // equivalent: using the index API
5505 const ryml::id_type doc0_id = tree.first_child(stream_id);
5506 CHECK(tree.is_doc(doc0_id));
5507 CHECK(tree.is_map(doc0_id));
5508 CHECK(tree.val(tree.find_child(doc0_id, "a")) == "0");
5509 CHECK(tree.val(tree.find_child(doc0_id, "b")) == "1");
5510
5511 // using the node API
5512 CHECK(stream[1].is_doc());
5513 CHECK(stream[1].is_map());
5514 CHECK(stream[1]["c"].val() == "2");
5515 CHECK(stream[1]["d"].val() == "3");
5516 // equivalent: using the index API
5517 const ryml::id_type doc1_id = tree.next_sibling(doc0_id);
5518 CHECK(tree.is_doc(doc1_id));
5519 CHECK(tree.is_map(doc1_id));
5520 CHECK(tree.val(tree.find_child(doc1_id, "c")) == "2");
5521 CHECK(tree.val(tree.find_child(doc1_id, "d")) == "3");
5522
5523 // using the node API
5524 CHECK(stream[2].is_doc());
5525 CHECK(stream[2].is_seq());
5526 CHECK(stream[2][0].val() == "4");
5527 CHECK(stream[2][1].val() == "5");
5528 CHECK(stream[2][2].val() == "6");
5529 CHECK(stream[2][3].val() == "7");
5530 // equivalent: using the index API
5531 const ryml::id_type doc2_id = tree.next_sibling(doc1_id);
5532 CHECK(tree.is_doc(doc2_id));
5533 CHECK(tree.is_seq(doc2_id));
5534 CHECK(tree.val(tree.child(doc2_id, 0)) == "4");
5535 CHECK(tree.val(tree.child(doc2_id, 1)) == "5");
5536 CHECK(tree.val(tree.child(doc2_id, 2)) == "6");
5537 CHECK(tree.val(tree.child(doc2_id, 3)) == "7");
5538 }
5539
5540 // Note: since json does not have streams, you cannot emit the above
5541 // tree as json when you start from the root:
5542 //CHECK(ryml::emitrs_json<std::string>(tree) == yml); // RUNTIME ERROR!
5543
5544 // but, althouth emitting streams as json is not possible,
5545 // you can iterate through individual documents and emit
5546 // them separately:
5547 {
5548 const std::string expected_json[] = {
5549 "{" "\n"
5550 " \"a\": 0," "\n"
5551 " \"b\": 1" "\n"
5552 "}" "\n"
5553 "",
5554 "{" "\n"
5555 " \"c\": 2," "\n"
5556 " \"d\": 3" "\n"
5557 "}" "\n"
5558 "",
5559 "[" "\n"
5560 " 4," "\n"
5561 " 5," "\n"
5562 " 6," "\n"
5563 " 7" "\n"
5564 "]" "\n"
5565 "",
5566 };
5567 // using the node API
5568 {
5569 ryml::id_type count = 0;
5570 const ryml::ConstNodeRef stream = tree.rootref();
5571 CHECK(stream.num_children() == (ryml::id_type)C4_COUNTOF(expected_json));
5572 for(ryml::ConstNodeRef doc : stream.children())
5573 {
5574 CHECK(ryml::emitrs_json<std::string>(doc) == expected_json[count++]);
5575 }
5576 }
5577 // equivalent: using the index API
5578 {
5579 ryml::id_type count = 0;
5580 const ryml::id_type stream_id = tree.root_id();
5581 CHECK(tree.num_children(stream_id) == (ryml::id_type)C4_COUNTOF(expected_json));
5582 for(ryml::id_type doc_id = tree.first_child(stream_id);
5583 doc_id != ryml::NONE;
5584 doc_id = tree.next_sibling(doc_id))
5585 {
5586 CHECK(ryml::emitrs_json<std::string>(tree, doc_id) == expected_json[count++]);
5587 }
5588 }
5589 }
5590}
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
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 5613 of file quickstart.cpp.

5614{
5615 ErrorHandlerExample errh; // browse the implementation of this
5616 // class to understand more details
5617 errh.check_disabled();
5618 // set the global error handlers. Note the error callbacks must
5619 // never return: they must either throw an exception, use setjmp()
5620 // and longjmp(), or abort. Otherwise, the parser will enter into
5621 // an infinite loop, or the program may crash.
5623 errh.check_enabled();
5624 CHECK(errh.check_error_occurs([&]{
5625 ryml::Tree tree = ryml::parse_in_arena("errorhandler.yml", "[a: b\n}");
5626 }));
5627 ryml::set_callbacks(errh.original_callbacks); // restore defaults.
5628 errh.check_disabled();
5629}
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 5634 of file quickstart.cpp.

5635{
5636 auto cause_basic_error = []{
5637 ryml::Tree t;
5638 ryml::csubstr tag_handle = {}, tag_prefix = {}; // invalid, not filled
5639 t.add_tag_directive(tag_handle, tag_prefix, 0);
5640 };
5641 {
5642 ScopedErrorHandlerExample errh; // set the example callbacks (scoped)
5643 CHECK(errh.check_error_occurs(cause_basic_error));
5644 }
5645#ifdef _RYML_WITH_EXCEPTIONS
5646 bool gotit = false;
5647 try
5648 {
5649 cause_basic_error();
5650 }
5651 catch(ryml::ExceptionBasic const& exc)
5652 {
5653 gotit = true;
5654 ryml::csubstr msg = ryml::to_csubstr(exc.what());
5656 CHECK(!msg.empty());
5657 }
5658 CHECK(gotit);
5659#endif
5660}
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 5663 of file quickstart.cpp.

5664{
5665 ryml::csubstr ymlsrc = ""
5666 "{" "\n"
5667 " a: b" "\n"
5668 " [" "\n"
5669 "";
5670 ryml::csubstr ymlfile = "file.yml";
5671 auto cause_parse_error = [&]{
5672 return ryml::parse_in_arena(ymlfile, ymlsrc);
5673 };
5674 // the YAML in ymlsrc must cause a parse error while it is being
5675 // parsed. We use our error handler to catch that error, and save
5676 // the error info:
5678 {
5680 CHECK(errh.check_error_occurs(cause_parse_error));
5681 // the handler in errh saves the error info in itself. Let's
5682 // use that to see the messages we get.
5683 //
5684 // this message is the short message passed into the parse
5685 // error handler:
5686 CHECK(errh.saved_msg_short == "invalid character: '['");
5687 // this message was created inside the handler, by calling
5688 // ryml::err_parse_format():
5689 CHECK(ryml::to_csubstr(errh.saved_msg_full).begins_with("file.yml:3: col=4 (12B): ERROR: [parse] invalid character: '['"));
5690 // If you keep the YAML source buffer around, you can also use
5691 // it to create/print a larger error message showing the
5692 // YAML source code context which causes the error:
5693 std::string msg_ctx = errh.saved_msg_full + "\n";
5695 msg_ctx.append(s.str, s.len);
5696 }, errh.saved_parse_loc, ymlsrc, "err");
5697 CHECK(ryml::to_csubstr(msg_ctx).begins_with("file.yml:3: col=4 (12B): ERROR: [parse] invalid character: '['"));
5698 CHECK(ryml::to_csubstr(msg_ctx).ends_with(
5699 "file.yml:3: col=4 (12B): err:" "\n"
5700 "err:" "\n"
5701 "err: [" "\n"
5702 "err: |" "\n"
5703 "err: (here)" "\n"
5704 "err:" "\n"
5705 "err: see region:" "\n"
5706 "err:" "\n"
5707 "err: {" "\n"
5708 "err: a: b" "\n"
5709 "err: [" "\n"
5710 "err: |" "\n"
5711 "err: (here)" "\n"
5712 ""));
5713 //
5714 // Let's now check the location (see the message above):
5715 CHECK(errh.saved_parse_loc.name == ymlfile);
5716 CHECK(errh.saved_parse_loc.line == 3);
5717 CHECK(errh.saved_parse_loc.col == 4);
5718 CHECK(errh.saved_parse_loc.offset == 12);
5719 CHECK(errh.saved_parse_loc.offset <= ymlsrc.len);
5720 // ... and this is the location in the ryml source code file where
5721 // this error was found:
5723 CHECK(errh.saved_basic_loc.line > 0);
5724 CHECK(errh.saved_basic_loc.col > 0);
5725 CHECK(errh.saved_basic_loc.offset > 0);
5727 }
5728 // A parse error is also a basic error. If no parse error handler
5729 // is set, then ryml falls back to a basic error:
5730 {
5731 ryml::Callbacks cb = errh.callbacks();
5732 cb.m_error_parse = nullptr;
5734 CHECK(ryml::get_callbacks().m_error_parse == nullptr);
5735 CHECK(errh.check_error_occurs(cause_parse_error));
5736 // we got a basic error instead of a parse error:
5737 CHECK(errh.saved_msg_short == "invalid character: '['");
5738 // notice that the full message now displays this as a basic
5739 // error:
5740 CHECK(errh.saved_msg_full == "file.yml:3: col=4 (12B): ERROR: [basic] invalid character: '['");
5741 // the yml location is now in the location saved from the basic error
5742 CHECK(errh.saved_basic_loc.name == ymlfile);
5743 CHECK(errh.saved_basic_loc.line == 3);
5744 CHECK(errh.saved_basic_loc.col == 4);
5745 CHECK(errh.saved_basic_loc.offset == 12);
5746 CHECK(errh.saved_basic_loc.offset <= ymlsrc.len);
5752 }
5753#ifdef _RYML_WITH_EXCEPTIONS
5754 bool gotit = false;
5755 try
5756 {
5757 cause_parse_error();
5758 }
5759 catch(ryml::ExceptionParse const& exc)
5760 {
5761 gotit = true;
5762 ryml::csubstr msg = ryml::to_csubstr(exc.what());
5763 CHECK(exc.errdata_parse.ymlloc.name == ymlfile);
5764 CHECK(exc.errdata_parse.ymlloc.line == 3);
5765 CHECK(exc.errdata_parse.ymlloc.col == 4);
5766 CHECK(exc.errdata_parse.ymlloc.offset == 12);
5767 CHECK(exc.errdata_parse.ymlloc.offset <= ymlsrc.len);
5768 // the message saved in the exception is just the concrete error description:
5769 CHECK(msg == "invalid character: '['");
5770 // to print richer error messages, ryml provides helpers to
5771 // format that description into a complete error message,
5772 // containing location and source context indication:
5773 std::string full;
5774 auto dumpfn = [&full](ryml::csubstr s) { full.append(s.str, s.len); };
5775 ryml::err_parse_format(dumpfn, msg, exc.errdata_parse);
5776 full += '\n';
5777 ryml::location_format_with_context(dumpfn, exc.errdata_parse.ymlloc, ymlsrc, "err", 3);
5778 CHECK(ryml::to_csubstr(full).begins_with("file.yml:3: col=4 (12B): ERROR: [parse] invalid character: '['"));
5779 CHECK(ryml::to_csubstr(full).ends_with(
5780 "file.yml:3: col=4 (12B): err:" "\n"
5781 "err:" "\n"
5782 "err: [" "\n"
5783 "err: |" "\n"
5784 "err: (here)" "\n"
5785 "err:" "\n"
5786 "err: see region:" "\n"
5787 "err:" "\n"
5788 "err: {" "\n"
5789 "err: a: b" "\n"
5790 "err: [" "\n"
5791 "err: |" "\n"
5792 "err: (here)" "\n"
5793 ""));
5794 }
5795 CHECK(gotit);
5796 gotit = false;
5797 try
5798 {
5799 cause_parse_error();
5800 }
5801 catch(ryml::ExceptionBasic const& exc) // use references! don't slice the exception
5802 {
5803 gotit = true;
5804 ryml::csubstr msg = ryml::to_csubstr(exc.what());
5806 CHECK(!msg.empty());
5807 }
5808 CHECK(gotit);
5809#endif
5810}
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 5815 of file quickstart.cpp.

5816{
5817 ryml::csubstr ymlfile = "file.yml";
5818 ryml::csubstr ymlsrc = "float: 123.456";
5820 {
5822 ryml::Tree tree = ryml::parse_in_arena(ymlfile, ymlsrc);
5823 CHECK(errh.check_error_occurs([&]{
5824 int intval = 0;
5825 tree["float"] >> intval; // cannot deserialize 123.456 to int
5826 }));
5827 // the handler in errh saves the error info in itself. Let's
5828 // use that to see the messages we get.
5829 //
5830 // this message is the short message passed into the visit error
5831 CHECK(errh.saved_msg_short == "could not deserialize value");
5832 // this message was created inside the handler, by calling
5833 // ryml::err_visit_format():
5834 CHECK(ryml::csubstr::npos != ryml::to_csubstr(errh.saved_msg_full).find("ERROR: [visit] could not deserialize value"));
5835 // The location of the visit error is of the C++ source file where
5836 // the error was detected -- NOT of the YAML source file:
5837 CHECK(errh.saved_basic_loc.name != ymlfile);
5838 // However, note that the tree and node id are available:
5839 CHECK(errh.saved_visit_tree == &tree);
5840 CHECK(errh.saved_visit_id == tree["float"].id());
5841 // see sample_error_visit_location() for an example on how
5842 // to extract the location.
5843 }
5844 // visit errors also fall back to basic errors when the visit
5845 // handler is not set (similar to the behavior of ExceptionVisit):
5846 {
5847 ryml::Callbacks cb = errh.callbacks();
5848 cb.m_error_visit = nullptr;
5850 CHECK(ryml::get_callbacks().m_error_visit == nullptr);
5851 ryml::Tree tree = ryml::parse_in_arena(ymlfile, ymlsrc);
5852 CHECK(errh.check_error_occurs([&]{
5853 int intval = 0;
5854 tree["float"] >> intval; // cannot deserialize 123.456 to int
5855 }));
5856 // we got a basic error instead of a visit error:
5857 CHECK(errh.saved_msg_short == "could not deserialize value");
5858 // notice that the full message now displays this as a basic
5859 // error:
5860 CHECK(ryml::csubstr::npos != ryml::to_csubstr(errh.saved_msg_full).find("ERROR: [basic] could not deserialize value"));
5861 // the tree and id are not set, because this was called as a basic error
5862 CHECK(errh.saved_visit_tree == nullptr);
5865 }
5866#ifdef _RYML_WITH_EXCEPTIONS
5867 // when using the default ryml callbacks (see
5868 // RYML_NO_DEFAULT_CALLBACKS), and
5869 // RYML_DEFAULT_CALLBACK_USES_EXCEPTIONS is defined, the ryml
5870 // parse handler throws an exception of type ryml::ExceptionVisit,
5871 // which is derived from ryml::ExceptionBasic.
5872 {
5873 const ryml::Tree tree = ryml::parse_in_arena(ymlfile, ymlsrc);
5874 bool gotit = false;
5875 try
5876 {
5877 int intval = 0;
5878 tree["float"] >> intval; // cannot deserialize 123.456 to int
5879 }
5880 catch(ryml::ExceptionVisit const& exc)
5881 {
5882 gotit = true;
5883 ryml::csubstr msg = ryml::to_csubstr(exc.what());
5885 CHECK(exc.errdata_visit.tree == &tree);
5886 CHECK(exc.errdata_visit.node == tree["float"].id());
5887 CHECK(!msg.empty());
5888 }
5889 CHECK(gotit);
5890 }
5891 // you can also catch the exception as its base,
5892 // ryml::ExceptionBasic:
5893 {
5894 const ryml::Tree tree = ryml::parse_in_arena(ymlfile, ymlsrc);
5895 bool gotit = false;
5896 try
5897 {
5898 int intval = 0;
5899 tree["float"] >> intval; // cannot deserialize 123.456 to int
5900 }
5901 catch(ryml::ExceptionBasic const& exc) // use references! don't slice the exception
5902 {
5903 gotit = true;
5904 ryml::csubstr msg = ryml::to_csubstr(exc.what());
5906 CHECK(!msg.empty());
5907 }
5908 CHECK(gotit);
5909 }
5910#endif
5911}
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 5919 of file quickstart.cpp.

5920{
5922 // we will use locations to show the YAML source context of the
5923 // node where the visit error was triggered. This is a very
5924 // convenient feature to show detailed messages when deserializing
5925 // data read from a file (but do note this is opt-in, and it is
5926 // not mandatory). See sample_location_tracking() for more details
5927 // on location tracking.
5929 ryml::EventHandlerTree evt_handler{};
5930 ryml::Parser parser(&evt_handler, opts);
5931 ryml::csubstr ymlfile = "file.yml";
5932 ryml::csubstr ymlsrc = ""
5933 "foo: bar" "\n"
5934 "char: a" "\n"
5935 "int: a" "\n"
5936 "float: 123.456" "\n"
5937 "";
5938 const ryml::Tree tree = ryml::parse_in_arena(&parser, ymlfile, ymlsrc);
5939 // This function will cause a visit error when being called:
5940 auto cause_visit_error = [&]{
5941 int intval = 0;
5942 tree["float"] >> intval; // cannot deserialize 123.456 to int
5943 };
5944 // Like with the parse error, we will use our error handler to
5945 // catch that visit error, and save the error info:
5946 CHECK(evt_handler.callbacks() == errh.callbacks());
5947 CHECK(parser.callbacks() == errh.callbacks());
5948 CHECK(tree.callbacks() == errh.callbacks());
5949 {
5950 CHECK(errh.check_error_occurs(cause_visit_error));
5951 // the handler in errh saves the error info in itself. Let's
5952 // use that to see the messages we get.
5953 //
5954 // this message is the short message passed into the visit error
5955 CHECK(errh.saved_msg_short == "could not deserialize value");
5956 // this message was created inside the handler, by calling
5957 // ryml::err_visit_format():
5958 CHECK(ryml::csubstr::npos != ryml::to_csubstr(errh.saved_msg_full).find("ERROR: [visit] could not deserialize value"));
5959 // The location of the visit error is of the C++ source file where
5960 // the error was detected -- NOT of the YAML source file:
5961 CHECK(errh.saved_basic_loc.name != ymlfile);
5962 // However, note that the tree and node id are available:
5963 CHECK(errh.saved_visit_tree == &tree);
5964 CHECK(errh.saved_visit_id == tree["float"].id());
5965 // ... which we can use to get the location in the YAML source
5966 // from the parser (but see @ref sample_location_tracking()):
5967 ryml::Location ymlloc = errh.saved_visit_tree->location(parser, errh.saved_visit_id);
5968 CHECK(ymlloc.name == ymlfile);
5969 // In turn, we can use format_location_context() to
5970 // print/create an error message pointing at the YAML source
5971 // code:
5972 std::string msg = errh.saved_msg_full;
5974 msg.append(s.str, s.len);
5975 }, ymlloc, ymlsrc, "err", /*number of lines to show before the error*/3);
5976 CHECK(ryml::to_csubstr(msg).ends_with(
5977 "file.yml:3: col=0 (24B): err:" "\n"
5978 "err:" "\n"
5979 "err: float: 123.456" "\n"
5980 "err: |" "\n"
5981 "err: (here)" "\n"
5982 "err:" "\n"
5983 "err: see region:" "\n"
5984 "err:" "\n"
5985 "err: foo: bar" "\n"
5986 "err: char: a" "\n"
5987 "err: int: a" "\n"
5988 "err: float: 123.456" "\n"
5989 "err: |" "\n"
5990 "err: (here)" "\n"
5991 ""));
5992 }
5993}
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 6093 of file quickstart.cpp.

6094{
6096
6097 // save the existing callbacks for restoring
6099
6100 // set to our callbacks
6102
6103 // verify that the allocator is in effect
6104 ryml::Callbacks const& current = ryml::get_callbacks();
6105 CHECK(current.m_allocate == &mem.s_allocate);
6106 CHECK(current.m_free == &mem.s_free);
6107
6108 // so far nothing was allocated
6109 CHECK(mem.alloc_size == 0);
6110
6111 // parse one tree and check
6112 (void)ryml::parse_in_arena(R"({foo: bar})");
6113 mem.check_and_reset();
6114
6115 // parse another tree and check
6116 (void)ryml::parse_in_arena(R"([a, b, c, d, {foo: bar, money: pennys}])");
6117 mem.check_and_reset();
6118
6119 // verify that by reserving we save allocations
6120 {
6121 ryml::EventHandlerTree evt_handler;
6122 ryml::Parser parser(&evt_handler); // reuse a parser
6123 ryml::Tree tree; // reuse a tree
6124
6125 tree.reserve(10); // reserve the number of nodes
6126 tree.reserve_arena(100); // reserve the arena size
6127 parser.reserve_stack(10); // reserve the parser depth.
6128
6129 // since the parser stack uses Small Storage Optimization,
6130 // allocations will only happen with capacities higher than 16.
6131 CHECK(mem.num_allocs == 2); // tree, tree_arena and NOT the parser
6132
6133 parser.reserve_stack(20); // reserve the parser depth.
6134 CHECK(mem.num_allocs == 3); // tree, tree_arena and now the parser as well
6135
6136 // verify that no other allocations occur when parsing
6137 size_t size_before = mem.alloc_size;
6138 parse_in_arena(&parser, "", R"([a, b, c, d, {foo: bar, money: pennys}])", &tree);
6139 CHECK(mem.alloc_size == size_before);
6140 CHECK(mem.num_allocs == 3);
6141 }
6142 mem.check_and_reset();
6143
6144 // restore defaults.
6145 ryml::set_callbacks(defaults);
6146}
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 6216 of file quickstart.cpp.

6217{
6221
6222 // the trees will use the memory in the resources above,
6223 // with each tree using a separate resource
6224 {
6225 // Watchout: ensure that the lifetime of the callbacks target
6226 // exceeds the lifetime of the tree.
6227 ryml::EventHandlerTree evt_handler(mrp.callbacks());
6228 ryml::Parser parser(&evt_handler);
6229 ryml::Tree tree1(mr1.callbacks());
6230 ryml::Tree tree2(mr2.callbacks());
6231
6232 ryml::csubstr yml1 = "{a: b}";
6233 ryml::csubstr yml2 = "{c: d, e: f, g: [h, i, 0, 1, 2, 3]}";
6234
6235 parse_in_arena(&parser, "file1.yml", yml1, &tree1);
6236 parse_in_arena(&parser, "file2.yml", yml2, &tree2);
6237 }
6238
6239 CHECK(mrp.num_allocs == 0); // YAML depth not large enough to warrant a parser allocation
6240 CHECK(mr1.alloc_size <= mr2.alloc_size); // because yml2 has more nodes
6241}
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 6250 of file quickstart.cpp.

6251{
6252 // Static trees may incur a static initialization order
6253 // problem. This happens because a default-constructed tree will
6254 // obtain the callbacks from the current global setting, which may
6255 // not have been initialized due to undefined static
6256 // initialization order:
6257 //
6258 // ERROR! depends on ryml::get_callbacks() which may not have been initialized.
6259 //static ryml::Tree tree;
6260 //
6261 // To work around the issue, declare static callbacks
6262 // to explicitly initialize the static tree:
6263 static ryml::Callbacks callbacks = default_callbacks(); // use default callback members
6264 static ryml::Tree tree(callbacks); // OK
6265 // now you can use the tree as normal:
6266 ryml::parse_in_arena(R"(doe: "a deer, a female deer")", &tree);
6267 CHECK(tree["doe"].val() == "a deer, a female deer");
6268}
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 6276 of file quickstart.cpp.

6277{
6278 // NOTE: locations are zero-based. If you intend to show the
6279 // location to a human user, you may want to pre-increment the line
6280 // and column by 1.
6281 ryml::csubstr yaml = ""
6282 "{" "\n"
6283 "aa: contents," "\n"
6284 "foo: [one, [two, three]]" "\n"
6285 "}" "\n"
6286 "";
6287 // A parser is needed to track locations, and it has to be
6288 // explicitly set to do it. Location tracking is disabled by
6289 // default.
6290 ryml::ParserOptions opts = {};
6291 opts.locations(true); // enable locations, default is false
6292 ryml::EventHandlerTree evt_handler = {};
6293 ryml::Parser parser(&evt_handler, opts);
6294 CHECK(parser.options().locations());
6295 // When locations are enabled, the first task while parsing will
6296 // consist of building and caching (in the parser) a
6297 // source-to-node lookup structure to accelerate location lookups.
6298 //
6299 // The cost of building the location accelerator is linear in the
6300 // size of the source buffer. This increased cost is the reason
6301 // for the opt-in requirement. When locations are disabled there
6302 // is no cost.
6303 //
6304 // Building the location accelerator may trigger an allocation,
6305 // but this can and should be avoided by reserving prior to
6306 // parsing:
6307 parser.reserve_locations(50u); // reserve for 50 lines
6308 // Now the structure will be built during parsing:
6309 ryml::Tree tree = parse_in_arena(&parser, "source.yml", yaml);
6310 // After this, we are ready to query the location from the parser:
6311 ryml::Location loc = tree.rootref().location(parser);
6312 // As for the complexity of the query: for large buffers it is
6313 // O(log(numlines)). For short source buffers (30 lines and less),
6314 // it is O(numlines), as a plain linear search is faster in this
6315 // case.
6316 CHECK(parser.location_contents(loc).begins_with("{"));
6317 CHECK(loc.offset == 0u);
6318 CHECK(loc.line == 0u);
6319 CHECK(loc.col == 0u);
6320 // on the next call, we only pay O(log(numlines)) because the
6321 // rebuild is already available:
6322 loc = tree["aa"].location(parser);
6323 CHECK(parser.location_contents(loc).begins_with("aa"));
6324 CHECK(loc.offset == 2u);
6325 CHECK(loc.line == 1u);
6326 CHECK(loc.col == 0u);
6327 // KEYSEQ in flow style: points at the key
6328 loc = tree["foo"].location(parser);
6329 CHECK(parser.location_contents(loc).begins_with("foo"));
6330 CHECK(loc.offset == 16u);
6331 CHECK(loc.line == 2u);
6332 CHECK(loc.col == 0u);
6333 loc = tree["foo"][0].location(parser);
6334 CHECK(parser.location_contents(loc).begins_with("one"));
6335 CHECK(loc.line == 2u);
6336 CHECK(loc.col == 6u);
6337 // SEQ in flow style: location points at the opening '[' (there's no key)
6338 loc = tree["foo"][1].location(parser);
6339 CHECK(parser.location_contents(loc).begins_with("["));
6340 CHECK(loc.line == 2u);
6341 CHECK(loc.col == 11u);
6342 loc = tree["foo"][1][0].location(parser);
6343 CHECK(parser.location_contents(loc).begins_with("two"));
6344 CHECK(loc.line == 2u);
6345 CHECK(loc.col == 12u);
6346 loc = tree["foo"][1][1].location(parser);
6347 CHECK(parser.location_contents(loc).begins_with("three"));
6348 CHECK(loc.line == 2u);
6349 CHECK(loc.col == 17u);
6350 // NOTE. The parser locations always point at the latest buffer to
6351 // be parsed with the parser object, so they must be queried using
6352 // the corresponding latest tree to be parsed. This means that if
6353 // the parser is reused, earlier trees will loose the possibility
6354 // of querying for location. It is undefined behavior to query the
6355 // parser for the location of a node from an earlier tree:
6356 ryml::Tree docval = parse_in_arena(&parser, "docval.yaml", "this is a docval");
6357 // From now on, none of the locations from the previous tree can
6358 // be queried:
6359 //loc = tree.rootref().location(parser); // ERROR, undefined behavior
6360 loc = docval.rootref().location(parser); // OK. this is the latest tree from this parser
6361 CHECK(parser.location_contents(loc).begins_with("this is a docval"));
6362 CHECK(loc.line == 0u);
6363 CHECK(loc.col == 0u);
6364
6365 // NOTES ABOUT CONTAINER LOCATIONS
6366 ryml::Tree tree2 = parse_in_arena(&parser, "containers.yaml",
6367 "" "\n"
6368 "a new: buffer" "\n"
6369 "to: be parsed" "\n"
6370 "map with key:" "\n"
6371 " first: value" "\n"
6372 " second: value" "\n"
6373 "seq with key:" "\n"
6374 " - first value" "\n"
6375 " - second value" "\n"
6376 " -" "\n"
6377 " - nested first value" "\n"
6378 " - nested second value" "\n"
6379 " -" "\n"
6380 " nested first: value" "\n"
6381 " nested second: value" "\n"
6382 "");
6383 // (Likewise, the docval tree can no longer be used to query.)
6384 //
6385 // For key-less block-style maps, the location of the container
6386 // points at the first child's key. For example, in this case
6387 // the root does not have a key, so its location is taken
6388 // to be at the first child:
6389 loc = tree2.rootref().location(parser);
6390 CHECK(parser.location_contents(loc).begins_with("a new"));
6391 CHECK(loc.offset == 1u);
6392 CHECK(loc.line == 1u);
6393 CHECK(loc.col == 0u);
6394 // note the first child points exactly at the same place:
6395 loc = tree2["a new"].location(parser);
6396 CHECK(parser.location_contents(loc).begins_with("a new"));
6397 CHECK(loc.offset == 1u);
6398 CHECK(loc.line == 1u);
6399 CHECK(loc.col == 0u);
6400 loc = tree2["to"].location(parser);
6401 CHECK(parser.location_contents(loc).begins_with("to"));
6402 CHECK(loc.line == 2u);
6403 CHECK(loc.col == 0u);
6404 // but of course, if the block-style map is a KEYMAP, then the
6405 // location is the map's key, and not the first child's key:
6406 loc = tree2["map with key"].location(parser);
6407 CHECK(parser.location_contents(loc).begins_with("map with key"));
6408 CHECK(loc.line == 3u);
6409 CHECK(loc.col == 0u);
6410 loc = tree2["map with key"]["first"].location(parser);
6411 CHECK(parser.location_contents(loc).begins_with("first"));
6412 CHECK(loc.line == 4u);
6413 CHECK(loc.col == 2u);
6414 loc = tree2["map with key"]["second"].location(parser);
6415 CHECK(parser.location_contents(loc).begins_with("second"));
6416 CHECK(loc.line == 5u);
6417 CHECK(loc.col == 2u);
6418 // same thing for KEYSEQ:
6419 loc = tree2["seq with key"].location(parser);
6420 CHECK(parser.location_contents(loc).begins_with("seq with key"));
6421 CHECK(loc.line == 6u);
6422 CHECK(loc.col == 0u);
6423 loc = tree2["seq with key"][0].location(parser);
6424 CHECK(parser.location_contents(loc).begins_with("first value"));
6425 CHECK(loc.line == 7u);
6426 CHECK(loc.col == 4u);
6427 loc = tree2["seq with key"][1].location(parser);
6428 CHECK(parser.location_contents(loc).begins_with("second value"));
6429 CHECK(loc.line == 8u);
6430 CHECK(loc.col == 4u);
6431 // SEQ nested in SEQ: container location points at the first child's "- " dash
6432 loc = tree2["seq with key"][2].location(parser);
6433 CHECK(parser.location_contents(loc).begins_with("- nested first value"));
6434 CHECK(loc.line == 10u);
6435 CHECK(loc.col == 4u);
6436 loc = tree2["seq with key"][2][0].location(parser);
6437 CHECK(parser.location_contents(loc).begins_with("nested first value"));
6438 CHECK(loc.line == 10u);
6439 CHECK(loc.col == 6u);
6440 // MAP nested in SEQ: same as above: point to key
6441 loc = tree2["seq with key"][3].location(parser);
6442 CHECK(parser.location_contents(loc).begins_with("nested first: "));
6443 CHECK(loc.line == 13u);
6444 CHECK(loc.col == 4u);
6445 loc = tree2["seq with key"][3][0].location(parser);
6446 CHECK(parser.location_contents(loc).begins_with("nested first: "));
6447 CHECK(loc.line == 13u);
6448 CHECK(loc.col == 4u);
6449}