rapidyaml 0.15.2
parse and emit YAML, and do it fast
Loading...
Searching...
No Matches
quickstart.cpp
Go to the documentation of this file.
1// ryml: quickstart
2
3/** @addtogroup doc_quickstart
4 *
5 * Best seen online at https://rapidyaml.readthedocs.io/v0.15.2/doxygen/
6 *
7 * This file does a quick tour of ryml. It has multiple self-contained
8 * and well-commented samples that illustrate how to use ryml, and how
9 * it works.
10 *
11 * Although this is not a unit test, the samples are written as a
12 * sequence of actions and predicate checks to better convey what is
13 * the expected result at any stage. And to ensure the code here is
14 * correct and up to date, it's also run as part of the CI tests.
15 *
16 * If something is unclear, please open an issue or send a pull
17 * request at https://github.com/biojppm/rapidyaml . If you have an
18 * issue while using ryml, it is also encouraged to try to reproduce
19 * the issue here, or look first through the relevant section.
20 *
21 * Happy ryml'ing!
22 *
23 *
24 * ### Note on use of raw strings
25 *
26 * This sample uses multiline C strings instead of C++11 R"(raw
27 * strings)" because the doxygen parser is badly broken when handling
28 * raw strings.
29 *
30 *
31 * ### Some guidance on building
32 *
33 * The directories that exist side-by-side with this file contain
34 * several examples on how to build this with cmake, such that you can
35 * hit the ground running. See [the relevant section of the main
36 * README](https://github.com/biojppm/rapidyaml/tree/v0.15.2?tab=readme-ov-file#quickstart-samples)
37 * for an overview of the different choices. I suggest starting first
38 * with the `add_subdirectory` example, treating it just like any
39 * other self-contained cmake project.
40 *
41 * Or very quickly, to build and run this sample on your PC, start by
42 * creating this `CMakeLists.txt`:
43 *
44 * @code{cmake}
45 * cmake_minimum_required(VERSION 3.13)
46 * project(ryml-quickstart LANGUAGES CXX)
47 * include(FetchContent)
48 * FetchContent_Declare(ryml
49 * GIT_REPOSITORY https://github.com/biojppm/rapidyaml.git
50 * GIT_TAG v0.15.2
51 * )
52 * FetchContent_MakeAvailable(ryml)
53 * add_executable(ryml-quickstart ${ryml_SOURCE_DIR}/samples/quickstart.cpp)
54 * target_link_libraries(ryml-quickstart ryml::ryml)
55 * add_custom_target(run ryml-quickstart
56 * COMMAND $<TARGET_FILE:ryml-quickstart>
57 * DEPENDS ryml-quickstart
58 * COMMENT "running: $<TARGET_FILE:ryml-quickstart>")
59 * @endcode
60 *
61 * Now run the following commands in the same folder:
62 *
63 * @code{bash}
64 * # configure the project
65 * cmake -S . -B build
66 * # build and run
67 * cmake --build build --target ryml-quickstart -j
68 * # optionally, open in your IDE
69 * cmake --open build
70 * @endcode
71 *
72 */
73
74//-----------------------------------------------------------------------------
75
76// CONTENTS:
77//
78// (Each function addresses a topic and is fully self-contained. Jump
79// to the function to find out about its topic.)
80/** @defgroup doc_quickstart_overview Overview
81 * @ingroup doc_quickstart
82 * @{ */
83void sample_lightning_overview(); ///< lightning overview of most common features
84void sample_quick_overview(); ///< quick overview of most common features
85/** @} */
86/** @defgroup doc_quickstart_substr Using csubstr
87 * @ingroup doc_quickstart
88 * @{ */
89void sample_substr(); ///< about ryml's string views (from c4core)
90/** @} */
91/** @defgroup doc_quickstart_parse Parsing
92 * @ingroup doc_quickstart
93 * @{ */
94void sample_parse_file(); ///< ready-to-go example of parsing a file from disk
95void sample_parse_in_place(); ///< parse a mutable YAML source buffer
96void sample_parse_in_arena(); ///< parse a read-only YAML source buffer
97void sample_parse_reuse_tree(); ///< parse into an existing tree, maybe into a node
98void sample_parse_reuse_parser(); ///< reuse an existing parser
99void sample_parse_reuse_tree_and_parser(); ///< how to reuse existing trees and parsers
100void sample_parse_style(); ///< shows how rapidyaml retains the style of parsed YAML
101/** @} */
102/** @defgroup doc_quickstart_tree Tree
103 * @ingroup doc_quickstart
104 * @{ */
105void sample_iterate_tree(); ///< visit individual nodes and iterate through trees
106void sample_location_tracking(); ///< track node YAML source locations in the parsed tree
107void sample_create_tree(); ///< programatically create trees
108void sample_create_tree_style(); ///< set node styles while creating trees
109void sample_tree_arena(); ///< interact with the tree's serialization arena
110/** @} */
111/** @defgroup doc_quickstart_serialization Serialization
112 * @ingroup doc_quickstart
113 * @{ */
114void sample_fundamental_types(); ///< serialize/deserialize fundamental types
115void sample_empty_null_values(); ///< serialize/deserialize/query empty or null values
116void sample_formatting(); ///< control formatting when serializing/deserializing
117void sample_base64(); ///< encode/decode base64
118void sample_serialize_basic(); ///< serialize/deserialize fundamental types
119void sample_user_scalar_types(); ///< serialize/deserialize scalar (leaf/scalar) types
120void sample_user_container_types(); ///< serialize/deserialize container (map or seq) types
121void sample_std_types(); ///< serialize/deserialize STL containers
122void sample_deserialize_error(); ///< shows error on deserializing nested nodes
123void sample_float_precision(); ///< control precision of serialized floats
124/** @} */
125/** @defgroup doc_quickstart_emit Emitting
126 * @ingroup doc_quickstart
127 * @{ */
128void sample_emit_to_container(); ///< emit to memory, eg a string or vector-like container
129void sample_emit_to_stream(); ///< emit to a stream, eg std::ostream
130void sample_emit_to_file(); ///< emit to a FILE*
131void sample_emit_nested_node(); ///< pick a nested node as the root when emitting
132/** @} */
133/** @defgroup doc_quickstart_json JSON
134 * @ingroup doc_quickstart
135 * @{ */
136void sample_json(); ///< JSON parsing and emitting
137/** @} */
138/** @defgroup doc_quickstart_style YAML styles
139 * @ingroup doc_quickstart
140 * @{ */
141void sample_style(); ///< query/set node styles
142void sample_style_flow_formatting();///< control formatting of flow containers
143void sample_style_flow_ml_indent(); ///< control indentation of FLOW_ML1 and FLOW_MLN containers
144/** @} */
145/** @defgroup doc_quickstart_anchors YAML anchors
146 * @ingroup doc_quickstart
147 * @{ */
148void sample_anchors_and_aliases(); ///< deal with YAML anchors and aliases
149void sample_anchors_and_aliases_create(); ///< how to create YAML anchors and aliases
150/** @} */
151/** @defgroup doc_quickstart_tags YAML tags
152 * @ingroup doc_quickstart
153 * @{ */
154void sample_tags(); ///< deal with YAML type tags
155void sample_tag_directives(); ///< deal with YAML tag namespace directives
156/** @} */
157/** @defgroup doc_quickstart_docs YAML documents
158 * @ingroup doc_quickstart
159 * @{ */
160void sample_docs(); ///< deal with YAML docs
161/** @} */
162/** @defgroup doc_quickstart_error Errors and error handlers
163 * @ingroup doc_quickstart
164 * @{ */
165void sample_error_handler(); ///< set custom error handlers
166void sample_error_basic(); ///< handler for basic errors, and obtain a full error message with basic context
167void sample_error_parse(); ///< handler for parse errors, and obtain a full error message with parse context
168void sample_error_visit(); ///< handler for visit errors, and obtain a full error message with visit context
169void sample_error_visit_location(); ///< obtaining the YAML location from a visit error
170/** @} */
171/** @defgroup doc_quickstart_allocators Allocators
172 * @ingroup doc_quickstart
173 * @{ */
174void sample_global_allocator(); ///< set a global allocator for ryml
175void sample_per_tree_allocator(); ///< set per-tree allocators
176/** @} */
177/** @defgroup doc_quickstart_static_trees Static trees
178 * @ingroup doc_quickstart
179 * @{ */
180void sample_static_trees(); ///< how to use static trees in ryml
181/** @} */
182
183
184
185//-----------------------------------------------------------------------------
186
187/** @defgroup doc_quickstart_helpers Sample helpers
188 *
189 * Helper utilities used in the quickstart.
190 * @ingroup doc_quickstart
191 */
192
193/** @cond dev */
194void handle_args(int, const char*[]);
195int report_checks();
196void ensure_callbacks();
197/** @endcond */
198
199// ryml can be used as a single header, or as a simple library:
200#if defined(RYML_SINGLE_HEADER) // using the single header directly in the executable
201 #define RYML_SINGLE_HDR_DEFINE_NOW
202 #include <ryml_all.hpp>
203#elif defined(RYML_SINGLE_HEADER_LIB) // using the single header from a library
204 #include <ryml_all.hpp>
205#else
206 // umbrella header bringing all of ryml. for compilation speed,
207 // prefer using individual includes.
208 #include <ryml.hpp>
209 // ryml itself does not use any STL container, so <ryml_std.hpp>
210 // is needed if interop with std containers is desired.
211 // This is an umbrella include bringing all the interop
212 // headers implemented in ryml; again, for compilation speed,
213 // prefer using individual includes.
214 #include <ryml_std.hpp> // optional header, provided for std:: interop
215 #include <c4/format.hpp> // needed for the examples below
216 #include <c4/format_base64.hpp> // needed for the examples below
217 // optional header to save/load files:
218 #include <c4/yml/file.hpp>
219 // optional header, definitions for error utilities to implement
220 // user-defined error callbacks:
221 #include <c4/yml/error.def.hpp>
222#endif
223
224
225// these are needed for the examples below
226#include <iostream>
227#include <sstream>
228#include <vector>
229#include <map>
230#ifdef C4_EXCEPTIONS
231#include <stdexcept>
232#else
233#include <csetjmp>
234#endif
235
236
237//-----------------------------------------------------------------------------
238
291
292
293//-----------------------------------------------------------------------------
294//-----------------------------------------------------------------------------
295//-----------------------------------------------------------------------------
296// first, some helpers used in this quickstart
297
298/** @addtogroup doc_quickstart_helpers
299 *
300 * @{ */
301
302/** used by @ref CHECK() */
303bool report_check(int line, const char *predicate, bool result);
304
305// define the CHECK macro: a glorified assert()
306#ifdef __DOXYGEN__
307/// a testing assertion, used only in this quickstart
308# define CHECK(predicate) assert(predicate) // enable doxygen to link to the functions called inside CHECK()
309#else
310// GCC 4.8 has some problems, notably with raw string arguments
311# if (defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 4) || (__GNUC_MINOR__ > 8))))
312# define CHECK(predicate) report_check(__LINE__, #predicate, (predicate))
313# else
314# define CHECK CheckPredicate{__LINE__}
315 namespace {
316 struct CheckPredicate
317 {
318 int line;
319 void operator() (bool result) const { report_check(line, nullptr, result); }
320 };
321 } // namespace
322# endif // GCC 4.8
323#endif // doxygen
324
325
326
327/** an error handler used by some of the quickstart examples.
328 * @ingroup doc_quickstart_helpers */
330{
332 ryml::Callbacks original_callbacks; // saves the original callbacks
333public:
334 // utilities used below
335 template<class Fn> bool check_error_occurs(Fn &&fn);
336 template<class Fn> bool check_assertion_occurs(Fn &&fn);
337 void check_enabled() const;
338 void check_disabled() const;
340public:
341 // these are the functions that we want to execute on error
342 [[noreturn]] void on_error_basic(ryml::csubstr msg, ryml::ErrorDataBasic const& errdata);
343 [[noreturn]] void on_error_parse(ryml::csubstr msg, ryml::ErrorDataParse const& errdata);
344 [[noreturn]] void on_error_visit(ryml::csubstr msg, ryml::ErrorDataVisit const& errdata);
345public:
346 // these are the trampoline functions that we set ryml to call
347 [[noreturn]] static void s_error_basic(ryml::csubstr msg, ryml::ErrorDataBasic const& errdata, void *this_);
348 [[noreturn]] static void s_error_parse(ryml::csubstr msg, ryml::ErrorDataParse const& errdata, void *this_);
349 [[noreturn]] static void s_error_visit(ryml::csubstr msg, ryml::ErrorDataVisit const& errdata, void *this_);
350public:
351 // the handlers save these fields to be able to check on them
352 // after the error is triggered. They are only valid after an
353 // error, and before the next error.
354 std::string saved_msg_short;
355 std::string saved_msg_full;
361};
362
363
364/** Shows how to create a scoped error handler.
365 * @ingroup doc_quickstart_helpers */
379
380// needed to setup the callbacks when ryml does not provide them
381void ensure_callbacks();
383
384
385C4_SUPPRESS_WARNING_GCC_CLANG_PUSH
386C4_SUPPRESS_WARNING_GCC_CLANG("-Wold-style-cast")
387C4_SUPPRESS_WARNING_GCC_CLANG("-Wcast-qual")
388C4_SUPPRESS_WARNING_GCC("-Wuseless-cast")
389C4_SUPPRESS_WARNING_GCC("-Wuseless-cast")
390#if defined(__GNUC__) && (__GNUC__ >= 6)
391C4_SUPPRESS_WARNING_GCC("-Wnull-dereference") // false positives
392#endif
393#if defined(__clang__) && (__clang_major__ >= 13)
394C4_SUPPRESS_WARNING_CLANG("-Wreserved-identifier")
395#endif
396
397
398/** @} */ // doc_quickstart_helpers
399
400
401//-----------------------------------------------------------------------------
402//-----------------------------------------------------------------------------
403//-----------------------------------------------------------------------------
404
405/** a lightning tour over most features
406 * see @ref sample_quick_overview */
408{
409 // Parse YAML code in place, potentially mutating the buffer:
410 char yml_buf[] = "{foo: 1, bar: [2, 3], john: doe}";
411 ryml::Tree tree = ryml::parse_in_place(yml_buf);
412
413 // read from the tree:
414 ryml::NodeRef bar = tree["bar"];
415 CHECK(bar[0].val() == "2");
416 CHECK(bar[1].val() == "3");
417 CHECK(bar[0].val().str == yml_buf + 15); // points at the source buffer
418 CHECK(bar[1].val().str == yml_buf + 18);
419
420 // deserializing:
421 int bar0 = 0, bar1 = 0;
422 bar[0].load(&bar0); // also checks the node is readable, and conversion succeeded
423 bar[1].load(&bar1); // see also .deserialize()
424 CHECK(bar0 == 2);
425 CHECK(bar1 == 3);
426
427 // serializing:
428 bar[0].save(10); // creates a string in the tree's arena
429 bar[1].save(11); // see also .set_serialized()
430 CHECK(bar[0].val() == "10");
431 CHECK(bar[1].val() == "11");
432
433 // add nodes
434 tree["new"].set_val("node");
435 bar.append_child().save(12);
436 CHECK(bar[2].val() == "12");
437
438 // emit tree
439 std::string expected = "{foo: 1,bar: [10,11,12],john: doe,new: node}";
440 // emit tree to std::string
441 CHECK(ryml::emitrs_yaml<std::string>(tree) == expected);
442 // emit tree to FILE*
443 ryml::emit_yaml(tree, stdout); printf("\n");
444 // emit tree to ostream
445 std::cout << tree << "\n";
446
447 // emit node
448 ryml::ConstNodeRef foo = tree["foo"];
449 expected = "foo: 1\n";
450 // emit node to std::string
451 CHECK(ryml::emitrs_yaml<std::string>(foo) == expected);
452 // emit node to FILE*
453 ryml::emit_yaml(foo, stdout);
454 // emit node to ostream
455 std::cout << foo;
456}
457
458
459//-----------------------------------------------------------------------------
460
461/** a brief tour over most features */
463{
464 // Parse YAML code in place, potentially mutating the buffer:
465 char yml_buf[] = ""
466 "foo: 1" "\n"
467 "bar: [2, 3]" "\n"
468 "john: doe" "\n"
469 "";
470 ryml::Tree tree = ryml::parse_in_place(yml_buf);
471
472 // The resulting tree contains only views to the parsed string. If
473 // the string was parsed in place, then the string must outlive
474 // the tree! This works here because both `yml_buf` and `tree`
475 // live on the same scope, so have the same lifetime.
476
477 // It is also possible to:
478 //
479 // - parse a read-only buffer using parse_in_arena(). This
480 // copies the YAML buffer to the tree's arena, and spares the
481 // headache of the string's lifetime.
482 //
483 // - reuse an existing tree (advised)
484 //
485 // - reuse an existing parser (advised)
486 //
487 // - parse into an existing node deep in a tree
488 //
489 // Note: it will always be significantly faster to parse in place
490 // and reuse tree+parser.
491 //
492 // Below you will find samples that show how to achieve reuse; but
493 // please note that for brevity and clarity, many of the examples
494 // here are parsing in the arena, and not reusing tree or parser.
495
496
497 //------------------------------------------------------------------
498 // API overview
499
500 // The ryml tree has a two-level API:
501 //
502 // The lower level index API is based on the indices of nodes,
503 // where the node's id is the node's position in the tree's data
504 // array. This API is very efficient, but somewhat difficult to use:
505 ryml::id_type root_id = tree.root_id();
506 ryml::id_type bar_id = tree.find_child(root_id, "bar"); // need to get the index right
507 CHECK(tree.is_map(root_id)); // all of the index methods are in the tree
508 CHECK(tree.is_seq(bar_id)); // ... and receive the subject index
509
510 // The node API is a lightweight abstraction sitting on top of the
511 // index API, but offering a much more convenient interaction:
512 ryml::ConstNodeRef root = tree.rootref(); // a const node reference
513 ryml::ConstNodeRef bar = tree["bar"];
514 CHECK(root.is_map());
515 CHECK(bar.is_seq());
516
517 // A node ref is a lightweight handle to the tree and associated id:
518 CHECK(root.tree() == &tree); // a node ref points at its tree, WITHOUT refcount
519 CHECK(root.id() == root_id); // a node ref's id is the index of the node
520 CHECK(bar.id() == bar_id); // a node ref's id is the index of the node
521
522 // The node API translates very cleanly to the index API, so most
523 // of the code examples below are using the node API.
524
525 // WARNING. A node ref holds a raw pointer to the tree. Care must
526 // be taken to ensure the lifetimes match, so that a node will
527 // never access the tree after the tree goes out of scope.
528
529
530 //------------------------------------------------------------------
531 // To read the parsed tree
532
533 // ConstNodeRef::operator[] does a lookup, is O(num_children[node]).
534 CHECK(tree["foo"].is_keyval());
535 CHECK(tree["foo"].val() == "1"); // get the val of a node (must be leaf node, otherwise it is a container and has no val)
536 CHECK(tree["foo"].key() == "foo"); // get the key of a node (must be child of a map, otherwise it has no key)
537 CHECK(tree["bar"].is_seq());
538 CHECK(tree["bar"].has_key());
539 CHECK(tree["bar"].key() == "bar");
540 // maps use string keys, seqs use index keys:
541 CHECK(tree["bar"][0].val() == "2");
542 CHECK(tree["bar"][1].val() == "3");
543 CHECK(tree["john"].val() == "doe");
544 // An index key is the position of the child within its parent,
545 // so even maps can also use int keys, if the key position is
546 // known.
547 CHECK(tree[0].id() == tree["foo"].id());
548 CHECK(tree[1].id() == tree["bar"].id());
549 CHECK(tree[2].id() == tree["john"].id());
550 // Tree::operator[](int) searches a ***root*** child by its position.
551 CHECK(tree[0].id() == tree["foo"].id()); // 0: first child of root
552 CHECK(tree[1].id() == tree["bar"].id()); // 1: second child of root
553 CHECK(tree[2].id() == tree["john"].id()); // 2: third child of root
554 // NodeRef::operator[](int) searches a ***node*** child by its position:
555 CHECK(bar[0].val() == "2"); // 0 means first child of bar
556 CHECK(bar[1].val() == "3"); // 1 means second child of bar
557 // NodeRef::operator[](string):
558 // A string key is the key of the node: lookup is by name. So it
559 // is only available for maps, and it is NOT available for seqs,
560 // since seq members do not have keys.
561 CHECK(tree["foo"].key() == "foo");
562 CHECK(tree["bar"].key() == "bar");
563 CHECK(tree["john"].key() == "john");
564 CHECK(bar.is_seq());
565 // CHECK(bar["BOOM!"].is_seed()); // error, seqs do not have key lookup
566
567 // Note that maps can also use index keys as well as string keys:
568 CHECK(root["foo"].id() == root[0].id());
569 CHECK(root["bar"].id() == root[1].id());
570 CHECK(root["john"].id() == root[2].id());
571
572 // IMPORTANT. The ryml tree uses an index-based linked list for
573 // storing children, so the complexity of
574 // `Tree::operator[csubstr]` and `Tree::operator[id_type]` is O(n),
575 // linear on the number of root children. If you use
576 // `Tree::operator[]` with a large tree where the root has many
577 // children, you will see a performance hit.
578 //
579 // To avoid this hit, you can create your own accelerator
580 // structure. For example, before doing a lookup, do a single
581 // traverse at the root level to fill an `map<csubstr,id_type>`
582 // mapping key names to node indices; with a node index, a lookup
583 // (via `Tree::get()`) is O(1), so this way you can get O(log n)
584 // lookup from a key. (But please do not use `std::map` if you
585 // care about performance; use something else like a flat map or
586 // sorted vector).
587 //
588 // As for node refs, the difference from `NodeRef::operator[]` and
589 // `ConstNodeRef::operator[]` to `Tree::operator[]` is that the
590 // latter refers to the root node, whereas the former are invoked
591 // on their target node. But the lookup process works the same for
592 // both and their algorithmic complexity is the same: they are
593 // both linear in the number of direct children. But of course,
594 // depending on the data, that number may be very different from
595 // one to another.
596
597
598 //------------------------------------------------------------------
599 // Hierarchy:
600
601 {
602 ryml::ConstNodeRef foo = root.first_child();
603 ryml::ConstNodeRef john = root.last_child();
604 CHECK(tree.size() == 6); // O(1) number of nodes in the tree
605 CHECK(root.num_children() == 3); // O(num_children[root])
606 CHECK(foo.num_siblings() == 3); // O(num_children[parent(foo)])
607 CHECK(foo.parent().id() == root.id()); // parent() is O(1)
608 CHECK(root.first_child().id() == root["foo"].id()); // first_child() is O(1)
609 CHECK(root.last_child().id() == root["john"].id()); // last_child() is O(1)
610 CHECK(john.first_sibling().id() == foo.id());
611 CHECK(foo.last_sibling().id() == john.id());
612 // prev_sibling(), next_sibling(): (both are O(1))
613 CHECK(foo.num_siblings() == root.num_children());
614 CHECK(foo.prev_sibling().id() == ryml::NONE); // foo is the first_child()
615 CHECK(foo.next_sibling().key() == "bar");
616 CHECK(foo.next_sibling().next_sibling().key() == "john");
617 CHECK(foo.next_sibling().next_sibling().next_sibling().id() == ryml::NONE); // john is the last_child()
618 }
619
620
621 //------------------------------------------------------------------
622 // Iterating:
623 {
624 ryml::csubstr expected_keys[] = {"foo", "bar", "john"};
625 // iterate children using the high-level node API:
626 {
627 ryml::id_type count = 0;
628 for(ryml::ConstNodeRef const& child : root.children())
629 CHECK(child.key() == expected_keys[count++]);
630 }
631 // iterate siblings using the high-level node API:
632 {
633 ryml::id_type count = 0;
634 for(ryml::ConstNodeRef const& child : root["foo"].siblings())
635 CHECK(child.key() == expected_keys[count++]);
636 }
637 // iterate children using the lower-level tree index API:
638 {
639 ryml::id_type count = 0;
640 for(ryml::id_type child_id = tree.first_child(root_id); child_id != ryml::NONE; child_id = tree.next_sibling(child_id))
641 CHECK(tree.key(child_id) == expected_keys[count++]);
642 }
643 // iterate siblings using the lower-level tree index API:
644 // (notice the only difference from above is in the loop
645 // preamble, which calls tree.first_sibling(bar_id) instead of
646 // tree.first_child(root_id))
647 {
648 ryml::id_type count = 0;
649 for(ryml::id_type child_id = tree.first_sibling(bar_id); child_id != ryml::NONE; child_id = tree.next_sibling(child_id))
650 CHECK(tree.key(child_id) == expected_keys[count++]);
651 }
652 }
653
654
655 //------------------------------------------------------------------
656 // Gotchas:
657
658 // ryml uses assertions to prevent you from trying to obtain
659 // things that do not exist. For example:
660
661 {
662 ryml::ConstNodeRef seq_node = tree["bar"];
663 ryml::ConstNodeRef val_node = seq_node[0];
664
665 CHECK(seq_node.is_seq()); // seq is a container
666 CHECK(!seq_node.has_val()); // ... so it has no val
667 //CHECK(seq_node.val() == BOOM!); // ... so attempting to get a val is undefined behavior
668
669 CHECK(val_node.parent() == seq_node); // belongs to a seq
670 CHECK(!val_node.has_key()); // ... so it has no key
671 //CHECK(val_node.key() == BOOM!); // ... so attempting to get a key is undefined behavior
672
673 CHECK(val_node.is_val()); // this node is a val
674 //CHECK(val_node.first_child() == BOOM!); // ... so attempting to get a child is undefined behavior
675
676 // assertions are also present in methods that /may/ read the val:
677 CHECK(seq_node.is_seq()); // seq is a container
678 //CHECK(seq_node.val_is_null() BOOM!); // so cannot get the val to check
679 }
680
681 // By default, assertions are enabled unless the NDEBUG macro is
682 // defined (which happens in release builds).
683 //
684 // This adheres to the pay-only-for-what-you-use philosophy: if
685 // you are sure that your intent is correct, why would you need to
686 // pay the runtime cost for the assertions?
687 //
688 // The downside, of course, is that you may be unsure, or your
689 // code may be wrong; and then release builds may end up doing
690 // something wrong.
691 //
692 // So in that case, you can use the appropriate ryml predicates to
693 // check your intent (as in the examples above), or you can
694 // explicitly enable/disable assertions, by defining the macro
695 // RYML_USE_ASSERT to a proper value (see c4/yml/common.hpp). This
696 // will make problematic code trigger a call to the appropriate
697 // error callback.
698 //
699 // Also, to be clear, this does not apply to parse errors
700 // occurring when the YAML is parsed. Parse errors are always
701 // detected by the parser: this is not done through assertions,
702 // but through the appropriate error checking mechanism. So
703 // checking for these errors is always enabled and cannot be
704 // switched off. As for deserialization errors, see below.
705
706
707 //------------------------------------------------------------------
708 // Deserializing:
709 //
710 // - use .load(T*)/.load_key(T*) for lazy code with checked reads
711 // - ryml checks preconditions (node readability, etc), or triggers error
712 // - ryml checks deserialization, or triggers error
713 // - more expensive because of all the checks and error marshalling
714 // - optionally, can disable precondition checks with bool parameter
715 //
716 // - use .deserialize()/.deserialize_key() for sure reads
717 // - ryml asserts preconditions (readability, etc) (err on debug builds, UB otherwise)
718 // - ryml returns bool for deserialization status, [[nodiscard]]
719 // - cheaper but potentially dangerous
720 //
721 {
722 // fundamental types: ryml provides deserialization facilities
723 // .load() checks for readability + conversion errors
724 {
725 int foo = 0, bar0 = 0, bar1 = 0;
726 root["foo"].load(&foo); // calls err_visit() on failure
727 root["bar"][0].load(&bar0);
728 root["bar"][1].load(&bar1);
729 CHECK(foo == 1);
730 CHECK(bar0 == 2);
731 CHECK(bar1 == 3);
732 // .load() can also be called from the tree
733 }
734 // .deserialize() asserts readability, user checks conversion errors
735 {
736 int foo = 0, bar0 = 0, bar1 = 0;
737 CHECK(root["foo"].deserialize(&foo)); // user must check return value
738 CHECK(root["bar"][0].deserialize(&bar0));
739 CHECK(root["bar"][1].deserialize(&bar1));
740 CHECK(foo == 1);
741 CHECK(bar0 == 2);
742 CHECK(bar1 == 3);
743 // .deserialize() can also be called from the tree
744 }
745 // std containers: ryml optionally provides deserialization
746 // facilities. see serialization samples below.
747 {
748 std::string john_str, bar_str;
749 root["john"].load(&john_str);
750 root["bar"].load_key(&bar_str);
751 CHECK(john_str == "doe");
752 CHECK(bar_str == "bar");
753 std::vector<int> bar_actual, bar_expected{{2, 3}};
754 root["bar"].load(&bar_actual);
755 CHECK(bar_actual == bar_expected);
756 }
757 }
758
759
760 //------------------------------------------------------------------
761 // Modifying existing nodes: assigning vs serializing
762
763 // As implied by its name, ConstNodeRef is a reference to a const
764 // node. It can be used to read from the node, but not write to it
765 // or modify the hierarchy of the node. If any modification is
766 // desired then a NodeRef must be used instead:
767 ryml::NodeRef wroot = tree.rootref(); // writeable root
768
769 // .set_val() assigns an existing string to the receiving node.
770 // The contents are NOT copied, and the string pointer will be in
771 // effect until the tree goes out of scope! So BEWARE to only
772 // assign from strings outliving the tree.
773 wroot["foo"].set_val("says you");
774 wroot["bar"][0].set_val("-2");
775 wroot["bar"][1].set_val("-3");
776 wroot["john"].set_val("ron");
777 // Now the tree is _pointing_ at the memory of the strings above.
778 // In this case it is OK because those are static strings, located
779 // in the executable's static section, and will outlive the tree.
780 CHECK(root["foo"].val() == "says you");
781 CHECK(root["bar"][0].val() == "-2");
782 CHECK(root["bar"][1].val() == "-3");
783 CHECK(root["john"].val() == "ron");
784 // But WATCHOUT: do not assign from temporary objects:
785 // {
786 // std::string bad("will dangle");
787 // root["john"] = bad;
788 // }
789 // CHECK(root["john"] == "dangling"); // CRASH! the string was deallocated
790
791 // For non-string types, or for strings whose lifetime is
792 // problematic WRT the tree, you can create and save a string in
793 // the tree using .set_serialized() (or its equivalent .save() if
794 // you want to have ryml do the error checking). It first
795 // serializes values to a string arena owned by the tree, then
796 // assigns the serialized string to the receiving node. This
797 // avoids constraints with the lifetime, since the arena lives
798 // with the tree.
799 CHECK(tree.arena().empty());
800 wroot["foo"].set_serialized("says who"); // requires to_chars(). see serialization samples below.
801 wroot["bar"][0].set_serialized(20);
802 wroot["bar"][1].set_serialized(30);
803 wroot["john"].set_serialized("deere");
804 CHECK(root["foo"].val() == "says who");
805 CHECK(root["bar"][0].val() == "20");
806 CHECK(root["bar"][1].val() == "30");
807 CHECK(root["john"].val() == "deere");
808 CHECK(tree.arena() == "says who2030deere"); // the result of serializations to the tree arena
809 // with .set_serialize()/.save(), the crash above is avoided:
810 {
811 std::string ok("in_scope");
812 // root["john"].set_val(ok); // don't, will dangle
813 wroot["john"].set_serialized(ok); // OK, copy to the tree's arena
814 }
815 CHECK(root["john"].val() == "in_scope"); // OK! val is now in the tree's arena
816 // serializing floating points:
817 wroot["float"].set_serialized(2.4f);
818 // to force a particular precision or float format:
819 // (see sample_float_precision() and sample_formatting())
820 wroot["digits"].set_serialized(ryml::fmt::real(2.4, /*num_digits*/6, ryml::FTOA_FLOAT));
821 CHECK(tree.arena() == "says who2030deerein_scope2.42.400000"); // the result of serializations to the tree arena
822
823
824 //------------------------------------------------------------------
825 // Adding new nodes:
826
827 // adding a keyval node to a map:
828 CHECK(root.num_children() == 5);
829 wroot["newkeyval"].set_val("shiny and new"); // using these strings
830 c4::yml::NodeRef chsrl = wroot.append_child();
831 chsrl.set_key_serialized("newkeyval (serialized)"); // serializes and assigns the serialization
832 chsrl.set_serialized("shiny and new (serialized)"); // serializes and assigns the serialization
833 CHECK(root.num_children() == 7);
834 CHECK(root["newkeyval"].key() == "newkeyval");
835 CHECK(root["newkeyval"].val() == "shiny and new");
836 CHECK(root["newkeyval (serialized)"].key() == "newkeyval (serialized)");
837 CHECK(root["newkeyval (serialized)"].val() == "shiny and new (serialized)");
838 CHECK( ! tree.in_arena(root["newkeyval"].key())); // it's using directly the static string above
839 CHECK( ! tree.in_arena(root["newkeyval"].val())); // it's using directly the static string above
840 CHECK( tree.in_arena(root["newkeyval (serialized)"].key())); // it's using a serialization of the string above
841 CHECK( tree.in_arena(root["newkeyval (serialized)"].val())); // it's using a serialization of the string above
842 // adding a val node to a seq:
843 CHECK(root["bar"].num_children() == 2);
844 wroot["bar"][2].set_val("oh so nice");
845 wroot["bar"][3].set_serialized("oh so nice (serialized)");
846 CHECK(root["bar"].num_children() == 4);
847 CHECK(root["bar"][2].val() == "oh so nice");
848 CHECK(root["bar"][3].val() == "oh so nice (serialized)");
849 // adding a seq node:
850 CHECK(root.num_children() == 7);
851 wroot["newseq"].set_seq();
852 chsrl = wroot.append_child();
853 chsrl.set_key_serialized("newseq (serialized)");
854 chsrl.set_seq();
855 CHECK(root.num_children() == 9);
856 CHECK(root["newseq"].num_children() == 0);
857 CHECK(root["newseq"].is_seq());
858 CHECK(root["newseq (serialized)"].num_children() == 0);
859 CHECK(root["newseq (serialized)"].is_seq());
860 // adding a map node:
861 CHECK(root.num_children() == 9);
862 wroot["newmap"].set_map();
863 chsrl = wroot.append_child();
864 chsrl.set_key_serialized("newmap (serialized)");
865 chsrl.set_map();
866 CHECK(root.num_children() == 11);
867 CHECK(root["newmap"].num_children() == 0);
868 CHECK(root["newmap"].is_map());
869 CHECK(root["newmap (serialized)"].num_children() == 0);
870 CHECK(root["newmap (serialized)"].is_map());
871 //
872 // When the tree is const, operator[] searches the tree for a
873 // matching key/position, and returns it, or raises an error if
874 // none is found.
875 //
876 // When the tree is mutable, operator[] will not mutate the tree
877 // until the returned node is written to. operator[] first
878 // searches the tree for a node matching the key/position and
879 // returns it if it exists; otherwise, the result is a NodeRef
880 // object which keeps in itself the required information to write
881 // to the proper place in the tree. This is called being in a
882 // "seed" state.
883 //
884 // This means that passing a key/position which does not exist
885 // will not mutate the tree, but will instead store (in the
886 // returned NodeRef) the proper place of the tree to be able to do
887 // so, if and when it is required. This is why the node is said to
888 // be in "seed" state - it allows creating the entry in the tree
889 // in the future.
890 //
891 // This is a significant difference from eg, the behavior of
892 // std::map, which mutates the map immediately within the call to
893 // operator[].
894 //
895 // All of the points above apply only if the tree is mutable. If
896 // the tree is const, then a NodeRef cannot be obtained from it;
897 // only a ConstNodeRef, which can never be used to mutate the
898 // tree.
899 //
900 CHECK(!root.has_child("I am not nothing"));
901 ryml::NodeRef nothing;
902 CHECK(nothing.invalid()); // invalid because it points at nothing
903 nothing = wroot["I am nothing"];
904 CHECK(!nothing.invalid()); // points at the tree, and a specific place in the tree
905 CHECK(nothing.is_seed()); // ... but nothing is there yet.
906 CHECK(!root.has_child("I am nothing")); // same as above
907 CHECK(!nothing.readable()); // ... and this node cannot be used to
908 // read anything from the tree
909 ryml::NodeRef something = wroot["I am something"];
910 ryml::ConstNodeRef constsomething = wroot["I am something"];
911 CHECK(!root.has_child("I am something")); // same as above
912 CHECK(!something.invalid());
913 CHECK(something.is_seed()); // same as above
914 CHECK(!something.readable()); // same as above
915 CHECK(constsomething.invalid()); // NOTE: because a ConstNodeRef cannot be
916 // used to mutate a tree, it is only valid()
917 // if it is pointing at an existing node.
918 something.set_val("indeed"); // this will commit the seed to the tree, mutating at the proper place
919 CHECK(root.has_child("I am something"));
920 CHECK(root["I am something"].val() == "indeed");
921 CHECK(!something.invalid()); // it was already valid
922 CHECK(!something.is_seed()); // now the tree has this node, so the
923 // ref is no longer a seed
924 CHECK(something.readable()); // and it is now readable
925 //
926 // now the constref is also valid (but it needs to be reassigned):
927 ryml::ConstNodeRef constsomethingnew = wroot["I am something"];
928 CHECK(!constsomethingnew.invalid());
929 CHECK(constsomethingnew.readable());
930 // note that the old constref is now stale, because it only keeps
931 // the state at creation:
932 CHECK(constsomething.invalid());
933 CHECK(!constsomething.readable());
934 //
935 // -----------------------------------------------------------
936 // Remember: a seed node cannot be used to read from the tree!
937 // -----------------------------------------------------------
938 //
939 // The seed node needs to be created and become readable first.
940 //
941 // Trying to invoke any tree-reading method on a node that is not
942 // readable will cause an assertion (see RYML_USE_ASSERT).
943 //
944 // It is your responsibility to verify that the preconditions are
945 // met. If you are not sure about the structure of your data,
946 // write your code defensively to signify your full intent:
947 //
948 ryml::NodeRef wbar = wroot["bar"];
949 if(wbar.readable() && wbar.is_seq()) // .is_seq() requires .readable()
950 {
951 CHECK(wbar[0].readable() && wbar[0].val() == "20");
952 CHECK( ! wbar[100].readable());
953 CHECK( ! wbar[100].readable() || wbar[100].val() == "100"); // <- no crash because it is not .readable(), so never tries to call .val()
954 // this would work as well:
955 CHECK( ! wbar[0].is_seed() && wbar[0].val() == "20");
956 CHECK(wbar[100].is_seed() || wbar[100].val() == "100");
957 }
958
959
960 //------------------------------------------------------------------
961 // .operator[]() vs .at()
962
963 // (Const)NodeRef::operator[] is an analogue to std::vector::operator[].
964 // (Const)NodeRef::at() is an analogue to std::vector::at()
965 //
966 // at() will always check the subject node is .readable().
967 //
968 // [] is meant for the happy path: unverified in Release builds,
969 // and asserts .readable() in Debug builds (more specifically when
970 // RYML_USE_ASSERT is defined and NDEBUG is not defined).
971 {
972 // in this example we will be checking errors, so set up a
973 // temporary error handler to catch them:
974 ScopedErrorHandlerExample errh; // calls ryml::set_callbacks()
975 // instantiate the tree after errh
976 ryml::Tree err_tree = ryml::parse_in_arena("{foo: bar}");
977 // ... so that the tree uses the current callbacks:
978 CHECK(err_tree.callbacks() == errh.callbacks());
979 // node does not exist, only a seed node
980 ryml::NodeRef seed_node = err_tree["this"];
981 // ... therefore not .readable()
982 CHECK(!seed_node.readable());
983 // using .at() reliably produces an error:
984 CHECK(errh.check_error_occurs([&]{
985 return seed_node.at("is").at("an").at("invalid").at("operation");
986 // ^
987 // error occurs here because it is unreadable
988 }));
989 // ... but using [] fails only when RYML_USE_ASSERT is
990 // defined. otherwise, it's the dreaded Undefined Behavior:
992 return seed_node["is"]["an"]["invalid"]["operation"];
993 // ^
994 // assertion occurs here because it is unreadable
995 }));
996 }
997
998
999 //------------------------------------------------------------------
1000 // Emitting:
1001
1002 ryml::csubstr expected_result =
1003 "foo: says who" "\n"
1004 "bar: [20,30,oh so nice,oh so nice (serialized)]" "\n"
1005 "john: in_scope" "\n"
1006 "float: 2.4" "\n"
1007 "digits: 2.400000" "\n"
1008 "newkeyval: shiny and new" "\n"
1009 "newkeyval (serialized): shiny and new (serialized)" "\n"
1010 "newseq: []" "\n"
1011 "newseq (serialized): []" "\n"
1012 "newmap: {}" "\n"
1013 "newmap (serialized): {}" "\n"
1014 "I am something: indeed" "\n"
1015 "";
1016
1017 // emit to a FILE*
1018 ryml::emit_yaml(tree, stdout);
1019 // emit to a stream
1020 // (this is templated on the stream, so it works both with
1021 // standard streams like std::ostream/std::stringstream, or with
1022 // user-defined streams not inheriting from ostream)
1023 std::stringstream ss;
1024 ss << tree;
1025 std::string stream_result = ss.str();
1026 // emit to a buffer:
1027 std::string str_result = ryml::emitrs_yaml<std::string>(tree);
1028 // can emit to any given buffer:
1029 char buf[1024];
1030 ryml::csubstr buf_result = ryml::emit_yaml(tree, buf);
1031 // now check
1032 CHECK(buf_result == expected_result);
1033 CHECK(str_result == expected_result);
1034 CHECK(stream_result == expected_result);
1035 // There are many possibilities to emit to buffer;
1036 // please look at the emit sample functions below.
1037
1038
1039 //------------------------------------------------------------------
1040 // ConstNodeRef vs NodeRef
1041
1042 ryml::NodeRef noderef = tree["bar"][0];
1043 ryml::ConstNodeRef constnoderef = tree["bar"][0];
1044
1045 // ConstNodeRef cannot be used to mutate the tree:
1046 //constnoderef.set_val("21"); // compile error: method does not exist
1047 //constnoderef.set_serialized("22"); // compile error
1048 // ... but a NodeRef can:
1049 noderef.set_val("21"); // ok, can assign because it's not const
1050 CHECK(tree["bar"][0].val() == "21");
1051 noderef.set_serialized("22"); // ok, can serialize and assign because it's not const
1052 CHECK(tree["bar"][0].val() == "22");
1053
1054 // it is not possible to obtain a NodeRef from a ConstNodeRef:
1055 // noderef = constnoderef; // compile error
1056
1057 // it is always possible to obtain a ConstNodeRef from a NodeRef:
1058 constnoderef = noderef; // ok can assign const <- nonconst
1059
1060 // If a tree is const, then only ConstNodeRef's can be
1061 // obtained from that tree:
1062 ryml::Tree const& consttree = tree;
1063 //noderef = consttree["bar"][0]; // compile error
1064 noderef = tree["bar"][0]; // ok
1065 constnoderef = consttree["bar"][0]; // ok
1066
1067 // ConstNodeRef and NodeRef can be compared for equality.
1068 // Equality means they point at the same node.
1069 CHECK(constnoderef == noderef);
1070 CHECK(!(constnoderef != noderef));
1071
1072
1073 //------------------------------------------------------------------
1074 // Getting the location of nodes in the source:
1075 //
1076 // Location tracking is opt-in:
1077 ryml::EventHandlerTree evt_handler = {};
1078 ryml::Parser parser(&evt_handler, ryml::ParserOptions().locations(true));
1079 // Now the parser will start by building the accelerator structure:
1080 ryml::Tree tree2 = parse_in_arena(&parser, "expected.yml", expected_result);
1081 // ... and use it when querying
1082 ryml::ConstNodeRef subject_node = tree2["bar"][1];
1083 CHECK(subject_node.val() == "30");
1084 ryml::Location loc = subject_node.location(parser);
1085 CHECK(parser.location_contents(loc).begins_with("30"));
1086 CHECK(loc.line == 1u);
1087 CHECK(loc.col == 9u);
1088 // For further details in location tracking,
1089 // refer to the sample function below.
1090
1091
1092 //------------------------------------------------------------------
1093 // Dealing with UTF8
1095 "#" "\n"
1096 "# UTF8/16/32 can be placed directly in any scalar:" "\n"
1097 "#" "\n"
1098 "en: Planet (Gas)" "\n"
1099 "fr: Planète (Gazeuse)" "\n"
1100 "ru: Планета (Газ)" "\n"
1101 "ja: 惑星(ガス)" "\n"
1102 "zh: 行星(气体)" "\n"
1103 "#" "\n"
1104 "# UTF8 decoding only happens in double-quoted strings," "\n"
1105 "# as per the YAML standard" "\n"
1106 "#" "\n"
1107 "decode this: \"\\u263A c\\x61f\\xE9\"" "\n"
1108 "and this as well: \"\\u2705 \\U0001D11E\"" "\n"
1109 "not decoded: '\\u263A \\xE2\\x98\\xBA'" "\n"
1110 "neither this: '\\u2705 \\U0001D11E'" "\n");
1111 // in-place UTF8 just works:
1112 CHECK(langs["en"].val() == "Planet (Gas)");
1113 CHECK(langs["fr"].val() == "Planète (Gazeuse)");
1114 CHECK(langs["ru"].val() == "Планета (Газ)");
1115 CHECK(langs["ja"].val() == "惑星(ガス)");
1116 CHECK(langs["zh"].val() == "行星(气体)");
1117 // and \x \u \U codepoints are decoded, but only when they appear
1118 // inside double-quoted strings, as dictated by the YAML
1119 // standard:
1120 CHECK(langs["decode this"].val() == "☺ café");
1121 CHECK(langs["and this as well"].val() == "✅ 𝄞");
1122 CHECK(langs["not decoded"].val() == "\\u263A \\xE2\\x98\\xBA");
1123 CHECK(langs["neither this"].val() == "\\u2705 \\U0001D11E");
1124}
1125
1126
1127//-----------------------------------------------------------------------------
1128
1129/** demonstrate usage of ryml::substr and ryml::csubstr
1130 *
1131 * These types are imported from the c4core library into the ryml
1132 * namespace You may have noticed above the use of a `csubstr`
1133 * class. This class is defined in another library,
1134 * [c4core](https://github.com/biojppm/c4core), which is imported by
1135 * ryml. This is a library I use with my projects consisting of
1136 * multiplatform low-level utilities. One of these is `c4::csubstr`
1137 * (the name comes from "constant substring") which is a non-owning
1138 * read-only string view, with many methods that make it practical to
1139 * use (I would certainly argue more practical than `std::string`). In
1140 * fact, `c4::csubstr` and its writeable counterpart `c4::substr` are
1141 * the workhorses of the ryml parsing and serialization code.
1142 *
1143 * @see doc_substr */
1145{
1146 // substr is a mutable view: pointer and length to a string in memory.
1147 // csubstr is a const-substr (immutable).
1148
1149 // construct from explicit args
1150 {
1151 const char foobar_str[] = "foobar";
1152 auto s = ryml::csubstr(foobar_str, strlen(foobar_str));
1153 CHECK(s == "foobar");
1154 CHECK(s.size() == 6);
1155 CHECK(s.data() == foobar_str);
1156 CHECK(s.size() == s.len);
1157 CHECK(s.data() == s.str);
1158 }
1159
1160 // construct from a string array
1161 {
1162 const char foobar_str[] = "foobar";
1163 ryml::csubstr s = foobar_str;
1164 CHECK(s == "foobar");
1165 CHECK(s != "foobar0");
1166 CHECK(s.size() == 6); // does not include the terminating \0
1167 CHECK(s.data() == foobar_str);
1168 CHECK(s.size() == s.len);
1169 CHECK(s.data() == s.str);
1170 }
1171 // you can also declare directly in-place from an array:
1172 {
1173 ryml::csubstr s = "foobar";
1174 CHECK(s == "foobar");
1175 CHECK(s != "foobar0");
1176 CHECK(s.size() == 6);
1177 CHECK(s.size() == s.len);
1178 CHECK(s.data() == s.str);
1179 }
1180
1181 // construct from a C-string:
1182 //
1183 // Since the input is only a pointer, the string length can only
1184 // be found with a call to strlen(). To make this cost evident, we
1185 // require calling to_csubstr():
1186 {
1187 const char *foobar_str = "foobar";
1188 ryml::csubstr s = ryml::to_csubstr(foobar_str);
1189 CHECK(s == "foobar");
1190 CHECK(s != "foobar0");
1191 CHECK(s.size() == 6);
1192 CHECK(s.size() == s.len);
1193 CHECK(s.data() == s.str);
1194 }
1195
1196 // construct from a std::string: same approach as above.
1197 // requires inclusion of the <ryml/std/string.hpp> header
1198 // or of the umbrella header <ryml_std.hpp>.
1199 //
1200 // not including <string> in the default header is a deliberate
1201 // design choice to avoid including the heavy std:: allocation
1202 // machinery
1203 {
1204 std::string foobar_str = "foobar";
1205 ryml::csubstr s = ryml::to_csubstr(foobar_str); // defined in <ryml/std/string.hpp>
1206 CHECK(s == "foobar");
1207 CHECK(s != "foobar0");
1208 CHECK(s.size() == 6);
1209 CHECK(s.size() == s.len);
1210 CHECK(s.data() == s.str);
1211 }
1212
1213 // convert substr -> csubstr
1214 {
1215 char buf[] = "foo";
1216 ryml::substr foo = buf;
1217 CHECK(foo.len == 3);
1218 CHECK(foo.data() == buf);
1219 ryml::csubstr cfoo = foo;
1220 CHECK(cfoo.data() == buf);
1221 }
1222 // cannot convert csubstr -> substr:
1223 {
1224 // ryml::substr foo2 = cfoo; // compile error: cannot write to csubstr
1225 }
1226
1227 // construct from char[]/const char[]: mutable vs immutable memory
1228 {
1229 char const foobar_str_ro[] = "foobar"; // ro := read-only
1230 char foobar_str_rw[] = "foobar"; // rw := read-write
1231 static_assert(std::is_array<decltype(foobar_str_ro)>::value, "this is an array");
1232 static_assert(std::is_array<decltype(foobar_str_rw)>::value, "this is an array");
1233 // csubstr <- read-only memory
1234 {
1235 ryml::csubstr foobar = foobar_str_ro;
1236 CHECK(foobar.data() == foobar_str_ro);
1237 CHECK(foobar.size() == strlen(foobar_str_ro));
1238 CHECK(foobar == "foobar"); // AKA strcmp
1239 }
1240 // csubstr <- read-write memory: you can create an immutable csubstr from mutable memory
1241 {
1242 ryml::csubstr foobar = foobar_str_rw;
1243 CHECK(foobar.data() == foobar_str_rw);
1244 CHECK(foobar.size() == strlen(foobar_str_rw));
1245 CHECK(foobar == "foobar"); // AKA strcmp
1246 }
1247 // substr <- read-write memory.
1248 {
1249 ryml::substr foobar = foobar_str_rw;
1250 CHECK(foobar.data() == foobar_str_rw);
1251 CHECK(foobar.size() == strlen(foobar_str_rw));
1252 CHECK(foobar == "foobar"); // AKA strcmp
1253 }
1254 // substr <- ro is impossible.
1255 {
1256 //ryml::substr foobar = foobar_str_ro; // compile error!
1257 }
1258 }
1259
1260 // construct from char*/const char*: mutable vs immutable memory.
1261 // use to_substr()/to_csubstr()
1262 {
1263 char const* foobar_str_ro = "foobar"; // ro := read-only
1264 char foobar_str_rw_[] = "foobar"; // rw := read-write
1265 char * foobar_str_rw = foobar_str_rw_; // rw := read-write
1266 static_assert(!std::is_array<decltype(foobar_str_ro)>::value, "this is a decayed pointer");
1267 static_assert(!std::is_array<decltype(foobar_str_rw)>::value, "this is a decayed pointer");
1268 // csubstr <- read-only memory
1269 {
1270 //ryml::csubstr foobar = foobar_str_ro; // compile error: length is not known
1271 ryml::csubstr foobar = ryml::to_csubstr(foobar_str_ro);
1272 CHECK(foobar.data() == foobar_str_ro);
1273 CHECK(foobar.size() == strlen(foobar_str_ro));
1274 CHECK(foobar == "foobar"); // AKA strcmp
1275 }
1276 // csubstr <- read-write memory: you can create an immutable csubstr from mutable memory
1277 {
1278 ryml::csubstr foobar = ryml::to_csubstr(foobar_str_rw);
1279 CHECK(foobar.data() == foobar_str_rw);
1280 CHECK(foobar.size() == strlen(foobar_str_rw));
1281 CHECK(foobar == "foobar"); // AKA strcmp
1282 }
1283 // substr <- read-write memory.
1284 {
1285 ryml::substr foobar = ryml::to_substr(foobar_str_rw);
1286 CHECK(foobar.data() == foobar_str_rw);
1287 CHECK(foobar.size() == strlen(foobar_str_rw));
1288 CHECK(foobar == "foobar"); // AKA strcmp
1289 }
1290 // substr <- read-only is impossible.
1291 {
1292 //ryml::substr foobar = ryml::to_substr(foobar_str_ro); // compile error!
1293 }
1294 }
1295
1296 // substr is mutable, without changing the size:
1297 {
1298 char buf[] = "foobar";
1299 ryml::substr foobar = buf;
1300 CHECK(foobar == "foobar");
1301 foobar[0] = 'F'; CHECK(foobar == "Foobar");
1302 foobar.back() = 'R'; CHECK(foobar == "FoobaR");
1303 foobar.reverse(); CHECK(foobar == "RabooF");
1304 foobar.reverse(); CHECK(foobar == "FoobaR");
1305 foobar.reverse_sub(1, 4); CHECK(foobar == "FabooR");
1306 foobar.reverse_sub(1, 4); CHECK(foobar == "FoobaR");
1307 foobar.reverse_range(2, 5); CHECK(foobar == "FoaboR");
1308 foobar.reverse_range(2, 5); CHECK(foobar == "FoobaR");
1309 foobar.replace('o', '0'); CHECK(foobar == "F00baR");
1310 foobar.replace('a', '_'); CHECK(foobar == "F00b_R");
1311 foobar.replace("_0b", 'a'); CHECK(foobar == "FaaaaR");
1312 foobar.toupper(); CHECK(foobar == "FAAAAR");
1313 foobar.tolower(); CHECK(foobar == "faaaar");
1314 foobar.fill('.'); CHECK(foobar == "......");
1315 // see also:
1316 // - .erase()
1317 // - .replace_all()
1318 }
1319
1320 // sub-views
1321 {
1322 ryml::csubstr s = "fooFOObarBAR";
1323 CHECK(s.len == 12u);
1324 // sub(): <- first,[num]
1325 CHECK(s.sub(0) == "fooFOObarBAR");
1326 CHECK(s.sub(0, 12) == "fooFOObarBAR");
1327 CHECK(s.sub(0, 3) == "foo" );
1328 CHECK(s.sub(3) == "FOObarBAR");
1329 CHECK(s.sub(3, 3) == "FOO" );
1330 CHECK(s.sub(6) == "barBAR");
1331 CHECK(s.sub(6, 3) == "bar" );
1332 CHECK(s.sub(9) == "BAR");
1333 CHECK(s.sub(9, 3) == "BAR");
1334 // first(): <- length
1335 CHECK(s.first(0) == "" );
1336 CHECK(s.first(1) == "f" );
1337 CHECK(s.first(2) != "f" );
1338 CHECK(s.first(2) == "fo" );
1339 CHECK(s.first(3) == "foo");
1340 // last(): <- length
1341 CHECK(s.last(0) == "");
1342 CHECK(s.last(1) == "R");
1343 CHECK(s.last(2) == "AR");
1344 CHECK(s.last(3) == "BAR");
1345 // range(): <- first, last
1346 CHECK(s.range(0, 12) == "fooFOObarBAR");
1347 CHECK(s.range(1, 12) == "ooFOObarBAR");
1348 CHECK(s.range(1, 11) == "ooFOObarBA" );
1349 CHECK(s.range(2, 10) == "oFOObarB" );
1350 CHECK(s.range(3, 9) == "FOObar" );
1351 // offs(): offset from beginning, end
1352 CHECK(s.offs(0, 0) == "fooFOObarBAR");
1353 CHECK(s.offs(1, 0) == "ooFOObarBAR");
1354 CHECK(s.offs(1, 1) == "ooFOObarBA" );
1355 CHECK(s.offs(2, 1) == "oFOObarBA" );
1356 CHECK(s.offs(2, 2) == "oFOObarB" );
1357 CHECK(s.offs(3, 3) == "FOObar" );
1358 // right_of(): <- pos, include_pos
1359 CHECK(s.right_of(0, true) == "fooFOObarBAR");
1360 CHECK(s.right_of(0, false) == "ooFOObarBAR");
1361 CHECK(s.right_of(1, true) == "ooFOObarBAR");
1362 CHECK(s.right_of(1, false) == "oFOObarBAR");
1363 CHECK(s.right_of(2, true) == "oFOObarBAR");
1364 CHECK(s.right_of(2, false) == "FOObarBAR");
1365 CHECK(s.right_of(3, true) == "FOObarBAR");
1366 CHECK(s.right_of(3, false) == "OObarBAR");
1367 // left_of() <- pos, include_pos
1368 CHECK(s.left_of(12, false) == "fooFOObarBAR");
1369 CHECK(s.left_of(11, true) == "fooFOObarBAR");
1370 CHECK(s.left_of(11, false) == "fooFOObarBA" );
1371 CHECK(s.left_of(10, true) == "fooFOObarBA" );
1372 CHECK(s.left_of(10, false) == "fooFOObarB" );
1373 CHECK(s.left_of( 9, true) == "fooFOObarB" );
1374 CHECK(s.left_of( 9, false) == "fooFOObar" );
1375 // left_of(),right_of() <- substr
1376 ryml::csubstr FOO = s.sub(3, 3);
1377 CHECK(s.is_super(FOO)); // required for the following
1378 CHECK(s.left_of(FOO) == "foo");
1379 CHECK(s.right_of(FOO) == "barBAR");
1380 }
1381
1382 // printing a substr/csubstr using printf-like
1383 {
1384 ryml::csubstr s = "some substring";
1385 ryml::csubstr some = s.first(4);
1386 CHECK(some == "some");
1387 CHECK(s == "some substring");
1388 // To print a csubstr using printf(), use the %.*s format specifier:
1389 {
1390 char result[32] = {0};
1391 std::snprintf(result, sizeof(result), "%.*s", (int)some.len, some.str);
1392 printf("~~~%s~~~\n", result);
1393 CHECK(ryml::to_csubstr((const char*)result) == "some");
1394 CHECK(ryml::to_csubstr((const char*)result) == some);
1395 }
1396 // But NOTE: because this is a string view type, in general
1397 // the C-string is NOT zero terminated. So NEVER printf it
1398 // directly, or it will overflow past the end of the given
1399 // substr, with a potential unbounded access. For example,
1400 // this is really bad:
1401 {
1402 char result[32] = {0};
1403 std::snprintf(result, sizeof(result), "%s", some.str); // ERROR! do not print the c-string directly
1404 CHECK(ryml::to_csubstr((const char*)result) == "some substring");
1405 CHECK(ryml::to_csubstr((const char*)result) == s);
1406 }
1407 }
1408
1409 // printing a substr/csubstr using ostreams
1410 {
1411 ryml::csubstr s = "some substring";
1412 ryml::csubstr some = s.first(4);
1413 CHECK(some == "some");
1414 CHECK(s == "some substring");
1415 // simple! just use plain operator<<
1416 {
1417 std::stringstream ss;
1418 ss << s;
1419 CHECK(ss.str() == "some substring"); // as expected
1420 CHECK(ss.str() == s); // as expected
1421 }
1422 // But NOTE: because this is a string view type, in general
1423 // the C-string is NOT zero terminated. So NEVER print it
1424 // directly, or it will overflow past the end of the given
1425 // substr, with a potential unbounded access. For example,
1426 // this is bad:
1427 {
1428 std::stringstream ss;
1429 ss << some.str; // ERROR! do not print the c-string directly
1430 CHECK(ss.str() == "some substring"); // NOT "some"
1431 CHECK(ss.str() == s); // NOT some
1432 }
1433 // this is also bad (the same)
1434 {
1435 std::stringstream ss;
1436 ss << some.data(); // ERROR! do not print the c-string directly
1437 CHECK(ss.str() == "some substring"); // NOT "some"
1438 CHECK(ss.str() == s); // NOT some
1439 }
1440 // this is ok:
1441 {
1442 std::stringstream ss;
1443 ss << some;
1444 CHECK(ss.str() == "some"); // ok
1445 CHECK(ss.str() == some); // ok
1446 }
1447 }
1448
1449 // is_sub(),is_super()
1450 {
1451 ryml::csubstr foobar = "foobar";
1452 ryml::csubstr foo = foobar.first(3);
1453 CHECK(foo.is_sub(foobar));
1454 CHECK(foo.is_sub(foo));
1455 CHECK(!foo.is_super(foobar));
1456 CHECK(!foobar.is_sub(foo));
1457 // identity comparison is true:
1458 CHECK(foo.is_super(foo));
1459 CHECK(foo.is_sub(foo));
1460 CHECK(foobar.is_sub(foobar));
1461 CHECK(foobar.is_super(foobar));
1462 }
1463
1464 // overlaps()
1465 {
1466 ryml::csubstr foobar = "foobar";
1467 ryml::csubstr foo = foobar.first(3);
1468 ryml::csubstr oba = foobar.offs(2, 1);
1469 ryml::csubstr abc = "abc";
1470 CHECK(foobar.overlaps(foo));
1471 CHECK(foobar.overlaps(oba));
1472 CHECK(foo.overlaps(foobar));
1473 CHECK(foo.overlaps(oba));
1474 CHECK(!foo.overlaps(abc));
1475 CHECK(!abc.overlaps(foo));
1476 }
1477
1478 // triml(): trim characters from the left
1479 // trimr(): trim characters from the right
1480 // trim(): trim characters from left AND right
1481 {
1482 CHECK(ryml::csubstr(" \t\n\rcontents without whitespace\t \n\r").trim("\t \n\r") == "contents without whitespace");
1483 ryml::csubstr aaabbb = "aaabbb";
1484 ryml::csubstr aaa___bbb = "aaa___bbb";
1485 // trim a character:
1486 CHECK(aaabbb.triml('a') == aaabbb.last(3)); // bbb
1487 CHECK(aaabbb.trimr('a') == aaabbb);
1488 CHECK(aaabbb.trim ('a') == aaabbb.last(3)); // bbb
1489 CHECK(aaabbb.triml('b') == aaabbb);
1490 CHECK(aaabbb.trimr('b') == aaabbb.first(3)); // aaa
1491 CHECK(aaabbb.trim ('b') == aaabbb.first(3)); // aaa
1492 CHECK(aaabbb.triml('c') == aaabbb);
1493 CHECK(aaabbb.trimr('c') == aaabbb);
1494 CHECK(aaabbb.trim ('c') == aaabbb);
1495 CHECK(aaa___bbb.triml('a') == aaa___bbb.last(6)); // ___bbb
1496 CHECK(aaa___bbb.trimr('a') == aaa___bbb);
1497 CHECK(aaa___bbb.trim ('a') == aaa___bbb.last(6)); // ___bbb
1498 CHECK(aaa___bbb.triml('b') == aaa___bbb);
1499 CHECK(aaa___bbb.trimr('b') == aaa___bbb.first(6)); // aaa___
1500 CHECK(aaa___bbb.trim ('b') == aaa___bbb.first(6)); // aaa___
1501 CHECK(aaa___bbb.triml('c') == aaa___bbb);
1502 CHECK(aaa___bbb.trimr('c') == aaa___bbb);
1503 CHECK(aaa___bbb.trim ('c') == aaa___bbb);
1504 // trim ANY of the characters:
1505 CHECK(aaabbb.triml("ab") == "");
1506 CHECK(aaabbb.trimr("ab") == "");
1507 CHECK(aaabbb.trim ("ab") == "");
1508 CHECK(aaabbb.triml("ba") == "");
1509 CHECK(aaabbb.trimr("ba") == "");
1510 CHECK(aaabbb.trim ("ba") == "");
1511 CHECK(aaabbb.triml("cd") == aaabbb);
1512 CHECK(aaabbb.trimr("cd") == aaabbb);
1513 CHECK(aaabbb.trim ("cd") == aaabbb);
1514 CHECK(aaa___bbb.triml("ab") == aaa___bbb.last(6)); // ___bbb
1515 CHECK(aaa___bbb.triml("ba") == aaa___bbb.last(6)); // ___bbb
1516 CHECK(aaa___bbb.triml("cd") == aaa___bbb);
1517 CHECK(aaa___bbb.trimr("ab") == aaa___bbb.first(6)); // aaa___
1518 CHECK(aaa___bbb.trimr("ba") == aaa___bbb.first(6)); // aaa___
1519 CHECK(aaa___bbb.trimr("cd") == aaa___bbb);
1520 CHECK(aaa___bbb.trim ("ab") == aaa___bbb.range(3, 6)); // ___
1521 CHECK(aaa___bbb.trim ("ba") == aaa___bbb.range(3, 6)); // ___
1522 CHECK(aaa___bbb.trim ("cd") == aaa___bbb);
1523 }
1524
1525 // unquoted():
1526 {
1527 CHECK(ryml::csubstr( "'this is is single quoted'" ).unquoted() == "this is is single quoted");
1528 CHECK(ryml::csubstr("\"this is is double quoted\"").unquoted() == "this is is double quoted");
1529 }
1530
1531 // stripl(): remove pattern from the left
1532 // stripr(): remove pattern from the right
1533 {
1534 ryml::csubstr abc___cba = "abc___cba";
1535 ryml::csubstr abc___abc = "abc___abc";
1536 CHECK(abc___cba.stripl("abc") == abc___cba.last(6)); // ___cba
1537 CHECK(abc___cba.stripr("abc") == abc___cba);
1538 CHECK(abc___cba.stripl("ab") == abc___cba.last(7)); // c___cba
1539 CHECK(abc___cba.stripr("ab") == abc___cba);
1540 CHECK(abc___cba.stripl("a") == abc___cba.last(8)); // bc___cba, same as triml('a')
1541 CHECK(abc___cba.stripr("a") == abc___cba.first(8));
1542 CHECK(abc___abc.stripl("abc") == abc___abc.last(6)); // ___abc
1543 CHECK(abc___abc.stripr("abc") == abc___abc.first(6)); // abc___
1544 CHECK(abc___abc.stripl("ab") == abc___abc.last(7)); // c___cba
1545 CHECK(abc___abc.stripr("ab") == abc___abc);
1546 CHECK(abc___abc.stripl("a") == abc___abc.last(8)); // bc___cba, same as triml('a')
1547 CHECK(abc___abc.stripr("a") == abc___abc);
1548 }
1549
1550 // begins_with()/ends_with()
1551 // begins_with_any()/ends_with_any()
1552 {
1553 ryml::csubstr s = "foobar123";
1554 // char overloads
1555 CHECK(s.begins_with('f'));
1556 CHECK(s.ends_with('3'));
1557 CHECK(!s.ends_with('2'));
1558 CHECK(!s.ends_with('o'));
1559 // char[] overloads
1560 CHECK(s.begins_with("foobar"));
1561 CHECK(s.begins_with("foo"));
1562 CHECK(s.begins_with_any("foo"));
1563 CHECK(!s.begins_with("oof"));
1564 CHECK(s.begins_with_any("oof"));
1565 CHECK(s.ends_with("23"));
1566 CHECK(s.ends_with("123"));
1567 CHECK(s.ends_with_any("123"));
1568 CHECK(!s.ends_with("321"));
1569 CHECK(s.ends_with_any("231"));
1570 }
1571
1572 // select()
1573 {
1574 ryml::csubstr s = "0123456789";
1575 CHECK(s.select('0') == s.sub(0, 1));
1576 CHECK(s.select('1') == s.sub(1, 1));
1577 CHECK(s.select('2') == s.sub(2, 1));
1578 CHECK(s.select('8') == s.sub(8, 1));
1579 CHECK(s.select('9') == s.sub(9, 1));
1580 CHECK(s.select("0123") == s.range(0, 4));
1581 CHECK(s.select("012" ) == s.range(0, 3));
1582 CHECK(s.select("01" ) == s.range(0, 2));
1583 CHECK(s.select("0" ) == s.range(0, 1));
1584 CHECK(s.select( "123") == s.range(1, 4));
1585 CHECK(s.select( "23") == s.range(2, 4));
1586 CHECK(s.select( "3") == s.range(3, 4));
1587 }
1588
1589 // find()
1590 {
1591 ryml::csubstr s012345 = "012345";
1592 // find single characters:
1593 CHECK(s012345.find('a') == ryml::npos);
1594 CHECK(s012345.find('0' ) == 0u);
1595 CHECK(s012345.find('0', 1u) == ryml::npos);
1596 CHECK(s012345.find('1' ) == 1u);
1597 CHECK(s012345.find('1', 2u) == ryml::npos);
1598 CHECK(s012345.find('2' ) == 2u);
1599 CHECK(s012345.find('2', 3u) == ryml::npos);
1600 CHECK(s012345.find('3' ) == 3u);
1601 CHECK(s012345.find('3', 4u) == ryml::npos);
1602 // find patterns
1603 CHECK(s012345.find("ab" ) == ryml::npos);
1604 CHECK(s012345.find("01" ) == 0u);
1605 CHECK(s012345.find("01", 1u) == ryml::npos);
1606 CHECK(s012345.find("12" ) == 1u);
1607 CHECK(s012345.find("12", 2u) == ryml::npos);
1608 CHECK(s012345.find("23" ) == 2u);
1609 CHECK(s012345.find("23", 3u) == ryml::npos);
1610 }
1611
1612 // count(): count the number of occurrences of a character
1613 {
1614 ryml::csubstr buf = "00110022003300440055";
1615 CHECK(buf.count('1' ) == 2u);
1616 CHECK(buf.count('1', 0u) == 2u);
1617 CHECK(buf.count('1', 1u) == 2u);
1618 CHECK(buf.count('1', 2u) == 2u);
1619 CHECK(buf.count('1', 3u) == 1u);
1620 CHECK(buf.count('1', 4u) == 0u);
1621 CHECK(buf.count('1', 5u) == 0u);
1622 CHECK(buf.count('0' ) == 10u);
1623 CHECK(buf.count('0', 0u) == 10u);
1624 CHECK(buf.count('0', 1u) == 9u);
1625 CHECK(buf.count('0', 2u) == 8u);
1626 CHECK(buf.count('0', 3u) == 8u);
1627 CHECK(buf.count('0', 4u) == 8u);
1628 CHECK(buf.count('0', 5u) == 7u);
1629 CHECK(buf.count('0', 6u) == 6u);
1630 CHECK(buf.count('0', 7u) == 6u);
1631 CHECK(buf.count('0', 8u) == 6u);
1632 CHECK(buf.count('0', 9u) == 5u);
1633 CHECK(buf.count('0', 10u) == 4u);
1634 CHECK(buf.count('0', 11u) == 4u);
1635 CHECK(buf.count('0', 12u) == 4u);
1636 CHECK(buf.count('0', 13u) == 3u);
1637 CHECK(buf.count('0', 14u) == 2u);
1638 CHECK(buf.count('0', 15u) == 2u);
1639 CHECK(buf.count('0', 16u) == 2u);
1640 CHECK(buf.count('0', 17u) == 1u);
1641 CHECK(buf.count('0', 18u) == 0u);
1642 CHECK(buf.count('0', 19u) == 0u);
1643 CHECK(buf.count('0', 20u) == 0u);
1644 }
1645
1646 // first_of(),last_of()
1647 {
1648 ryml::csubstr s012345 = "012345";
1649 CHECK(s012345.first_of('a') == ryml::npos);
1650 CHECK(s012345.first_of("ab") == ryml::npos);
1651 CHECK(s012345.first_of('0') == 0u);
1652 CHECK(s012345.first_of("0") == 0u);
1653 CHECK(s012345.first_of("01") == 0u);
1654 CHECK(s012345.first_of("10") == 0u);
1655 CHECK(s012345.first_of("012") == 0u);
1656 CHECK(s012345.first_of("210") == 0u);
1657 CHECK(s012345.first_of("0123") == 0u);
1658 CHECK(s012345.first_of("3210") == 0u);
1659 CHECK(s012345.first_of("01234") == 0u);
1660 CHECK(s012345.first_of("43210") == 0u);
1661 CHECK(s012345.first_of("012345") == 0u);
1662 CHECK(s012345.first_of("543210") == 0u);
1663 CHECK(s012345.first_of('5') == 5u);
1664 CHECK(s012345.first_of("5") == 5u);
1665 CHECK(s012345.first_of("45") == 4u);
1666 CHECK(s012345.first_of("54") == 4u);
1667 CHECK(s012345.first_of("345") == 3u);
1668 CHECK(s012345.first_of("543") == 3u);
1669 CHECK(s012345.first_of("2345") == 2u);
1670 CHECK(s012345.first_of("5432") == 2u);
1671 CHECK(s012345.first_of("12345") == 1u);
1672 CHECK(s012345.first_of("54321") == 1u);
1673 CHECK(s012345.first_of("012345") == 0u);
1674 CHECK(s012345.first_of("543210") == 0u);
1675 CHECK(s012345.first_of('0', 6u) == ryml::npos);
1676 CHECK(s012345.first_of('5', 6u) == ryml::npos);
1677 CHECK(s012345.first_of("012345", 6u) == ryml::npos);
1678 //
1679 CHECK(s012345.last_of('a') == ryml::npos);
1680 CHECK(s012345.last_of("ab") == ryml::npos);
1681 CHECK(s012345.last_of('0') == 0u);
1682 CHECK(s012345.last_of("0") == 0u);
1683 CHECK(s012345.last_of("01") == 1u);
1684 CHECK(s012345.last_of("10") == 1u);
1685 CHECK(s012345.last_of("012") == 2u);
1686 CHECK(s012345.last_of("210") == 2u);
1687 CHECK(s012345.last_of("0123") == 3u);
1688 CHECK(s012345.last_of("3210") == 3u);
1689 CHECK(s012345.last_of("01234") == 4u);
1690 CHECK(s012345.last_of("43210") == 4u);
1691 CHECK(s012345.last_of("012345") == 5u);
1692 CHECK(s012345.last_of("543210") == 5u);
1693 CHECK(s012345.last_of('5') == 5u);
1694 CHECK(s012345.last_of("5") == 5u);
1695 CHECK(s012345.last_of("45") == 5u);
1696 CHECK(s012345.last_of("54") == 5u);
1697 CHECK(s012345.last_of("345") == 5u);
1698 CHECK(s012345.last_of("543") == 5u);
1699 CHECK(s012345.last_of("2345") == 5u);
1700 CHECK(s012345.last_of("5432") == 5u);
1701 CHECK(s012345.last_of("12345") == 5u);
1702 CHECK(s012345.last_of("54321") == 5u);
1703 CHECK(s012345.last_of("012345") == 5u);
1704 CHECK(s012345.last_of("543210") == 5u);
1705 CHECK(s012345.last_of('0', 6u) == 0u);
1706 CHECK(s012345.last_of('5', 6u) == 5u);
1707 CHECK(s012345.last_of("012345", 6u) == 5u);
1708 }
1709
1710 // first_not_of(), last_not_of()
1711 {
1712 ryml::csubstr s012345 = "012345";
1713 CHECK(s012345.first_not_of('a') == 0u);
1714 CHECK(s012345.first_not_of("ab") == 0u);
1715 CHECK(s012345.first_not_of('0') == 1u);
1716 CHECK(s012345.first_not_of("0") == 1u);
1717 CHECK(s012345.first_not_of("01") == 2u);
1718 CHECK(s012345.first_not_of("10") == 2u);
1719 CHECK(s012345.first_not_of("012") == 3u);
1720 CHECK(s012345.first_not_of("210") == 3u);
1721 CHECK(s012345.first_not_of("0123") == 4u);
1722 CHECK(s012345.first_not_of("3210") == 4u);
1723 CHECK(s012345.first_not_of("01234") == 5u);
1724 CHECK(s012345.first_not_of("43210") == 5u);
1725 CHECK(s012345.first_not_of("012345") == ryml::npos);
1726 CHECK(s012345.first_not_of("543210") == ryml::npos);
1727 CHECK(s012345.first_not_of('5') == 0u);
1728 CHECK(s012345.first_not_of("5") == 0u);
1729 CHECK(s012345.first_not_of("45") == 0u);
1730 CHECK(s012345.first_not_of("54") == 0u);
1731 CHECK(s012345.first_not_of("345") == 0u);
1732 CHECK(s012345.first_not_of("543") == 0u);
1733 CHECK(s012345.first_not_of("2345") == 0u);
1734 CHECK(s012345.first_not_of("5432") == 0u);
1735 CHECK(s012345.first_not_of("12345") == 0u);
1736 CHECK(s012345.first_not_of("54321") == 0u);
1737 CHECK(s012345.first_not_of("012345") == ryml::npos);
1738 CHECK(s012345.first_not_of("543210") == ryml::npos);
1739 CHECK(s012345.last_not_of('a') == 5u);
1740 CHECK(s012345.last_not_of("ab") == 5u);
1741 CHECK(s012345.last_not_of('5') == 4u);
1742 CHECK(s012345.last_not_of("5") == 4u);
1743 CHECK(s012345.last_not_of("45") == 3u);
1744 CHECK(s012345.last_not_of("54") == 3u);
1745 CHECK(s012345.last_not_of("345") == 2u);
1746 CHECK(s012345.last_not_of("543") == 2u);
1747 CHECK(s012345.last_not_of("2345") == 1u);
1748 CHECK(s012345.last_not_of("5432") == 1u);
1749 CHECK(s012345.last_not_of("12345") == 0u);
1750 CHECK(s012345.last_not_of("54321") == 0u);
1751 CHECK(s012345.last_not_of("012345") == ryml::npos);
1752 CHECK(s012345.last_not_of("543210") == ryml::npos);
1753 CHECK(s012345.last_not_of('0') == 5u);
1754 CHECK(s012345.last_not_of("0") == 5u);
1755 CHECK(s012345.last_not_of("01") == 5u);
1756 CHECK(s012345.last_not_of("10") == 5u);
1757 CHECK(s012345.last_not_of("012") == 5u);
1758 CHECK(s012345.last_not_of("210") == 5u);
1759 CHECK(s012345.last_not_of("0123") == 5u);
1760 CHECK(s012345.last_not_of("3210") == 5u);
1761 CHECK(s012345.last_not_of("01234") == 5u);
1762 CHECK(s012345.last_not_of("43210") == 5u);
1763 CHECK(s012345.last_not_of("012345") == ryml::npos);
1764 CHECK(s012345.last_not_of("543210") == ryml::npos);
1765 }
1766
1767 // first_non_empty_span()
1768 {
1769 CHECK(ryml::csubstr("foo bar").first_non_empty_span() == "foo");
1770 CHECK(ryml::csubstr(" foo bar").first_non_empty_span() == "foo");
1771 CHECK(ryml::csubstr("\n \r \t foo bar").first_non_empty_span() == "foo");
1772 CHECK(ryml::csubstr("\n \r \t foo\n\r\t bar").first_non_empty_span() == "foo");
1773 CHECK(ryml::csubstr("\n \r \t foo\n\r\t bar").first_non_empty_span() == "foo");
1774 CHECK(ryml::csubstr(",\n \r \t foo\n\r\t bar").first_non_empty_span() == ",");
1775 }
1776 // first_uint_span()
1777 {
1778 CHECK(ryml::csubstr("1234 asdkjh").first_uint_span() == "1234");
1779 CHECK(ryml::csubstr("1234\rasdkjh").first_uint_span() == "1234");
1780 CHECK(ryml::csubstr("1234\tasdkjh").first_uint_span() == "1234");
1781 CHECK(ryml::csubstr("1234\nasdkjh").first_uint_span() == "1234");
1782 CHECK(ryml::csubstr("1234]asdkjh").first_uint_span() == "1234");
1783 CHECK(ryml::csubstr("1234)asdkjh").first_uint_span() == "1234");
1784 CHECK(ryml::csubstr("1234gasdkjh").first_uint_span() == "");
1785 }
1786 // first_int_span()
1787 {
1788 CHECK(ryml::csubstr("-1234 asdkjh").first_int_span() == "-1234");
1789 CHECK(ryml::csubstr("-1234\rasdkjh").first_int_span() == "-1234");
1790 CHECK(ryml::csubstr("-1234\tasdkjh").first_int_span() == "-1234");
1791 CHECK(ryml::csubstr("-1234\nasdkjh").first_int_span() == "-1234");
1792 CHECK(ryml::csubstr("-1234]asdkjh").first_int_span() == "-1234");
1793 CHECK(ryml::csubstr("-1234)asdkjh").first_int_span() == "-1234");
1794 CHECK(ryml::csubstr("-1234gasdkjh").first_int_span() == "");
1795 }
1796 // first_real_span()
1797 {
1798 CHECK(ryml::csubstr("-1234 asdkjh").first_real_span() == "-1234");
1799 CHECK(ryml::csubstr("-1234\rasdkjh").first_real_span() == "-1234");
1800 CHECK(ryml::csubstr("-1234\tasdkjh").first_real_span() == "-1234");
1801 CHECK(ryml::csubstr("-1234\nasdkjh").first_real_span() == "-1234");
1802 CHECK(ryml::csubstr("-1234]asdkjh").first_real_span() == "-1234");
1803 CHECK(ryml::csubstr("-1234)asdkjh").first_real_span() == "-1234");
1804 CHECK(ryml::csubstr("-1234gasdkjh").first_real_span() == "");
1805 CHECK(ryml::csubstr("1.234 asdkjh").first_real_span() == "1.234");
1806 CHECK(ryml::csubstr("1.234e+5 asdkjh").first_real_span() == "1.234e+5");
1807 CHECK(ryml::csubstr("1.234e-5 asdkjh").first_real_span() == "1.234e-5");
1808 CHECK(ryml::csubstr("1.234 asdkjh").first_real_span() == "1.234");
1809 CHECK(ryml::csubstr("1.234e+5 asdkjh").first_real_span() == "1.234e+5");
1810 CHECK(ryml::csubstr("1.234e-5 asdkjh").first_real_span() == "1.234e-5");
1811 CHECK(ryml::csubstr("-1.234 asdkjh").first_real_span() == "-1.234");
1812 CHECK(ryml::csubstr("-1.234e+5 asdkjh").first_real_span() == "-1.234e+5");
1813 CHECK(ryml::csubstr("-1.234e-5 asdkjh").first_real_span() == "-1.234e-5");
1814 // hexadecimal real numbers
1815 CHECK(ryml::csubstr("0x1.e8480p+19 asdkjh").first_real_span() == "0x1.e8480p+19");
1816 CHECK(ryml::csubstr("0x1.e8480p-19 asdkjh").first_real_span() == "0x1.e8480p-19");
1817 CHECK(ryml::csubstr("-0x1.e8480p+19 asdkjh").first_real_span() == "-0x1.e8480p+19");
1818 CHECK(ryml::csubstr("-0x1.e8480p-19 asdkjh").first_real_span() == "-0x1.e8480p-19");
1819 CHECK(ryml::csubstr("+0x1.e8480p+19 asdkjh").first_real_span() == "+0x1.e8480p+19");
1820 CHECK(ryml::csubstr("+0x1.e8480p-19 asdkjh").first_real_span() == "+0x1.e8480p-19");
1821 // binary real numbers
1822 CHECK(ryml::csubstr("0b101.011p+19 asdkjh").first_real_span() == "0b101.011p+19");
1823 CHECK(ryml::csubstr("0b101.011p-19 asdkjh").first_real_span() == "0b101.011p-19");
1824 CHECK(ryml::csubstr("-0b101.011p+19 asdkjh").first_real_span() == "-0b101.011p+19");
1825 CHECK(ryml::csubstr("-0b101.011p-19 asdkjh").first_real_span() == "-0b101.011p-19");
1826 CHECK(ryml::csubstr("+0b101.011p+19 asdkjh").first_real_span() == "+0b101.011p+19");
1827 CHECK(ryml::csubstr("+0b101.011p-19 asdkjh").first_real_span() == "+0b101.011p-19");
1828 // octal real numbers
1829 CHECK(ryml::csubstr("0o173.045p+19 asdkjh").first_real_span() == "0o173.045p+19");
1830 CHECK(ryml::csubstr("0o173.045p-19 asdkjh").first_real_span() == "0o173.045p-19");
1831 CHECK(ryml::csubstr("-0o173.045p+19 asdkjh").first_real_span() == "-0o173.045p+19");
1832 CHECK(ryml::csubstr("-0o173.045p-19 asdkjh").first_real_span() == "-0o173.045p-19");
1833 CHECK(ryml::csubstr("+0o173.045p+19 asdkjh").first_real_span() == "+0o173.045p+19");
1834 CHECK(ryml::csubstr("+0o173.045p-19 asdkjh").first_real_span() == "+0o173.045p-19");
1835 }
1836 // see also is_number()
1837
1838 // basename(), dirname(), extshort(), extlong()
1839 {
1840 CHECK(ryml::csubstr("/path/to/file.tar.gz").basename() == "file.tar.gz");
1841 CHECK(ryml::csubstr("/path/to/file.tar.gz").dirname() == "/path/to/");
1842 CHECK(ryml::csubstr("C:\\path\\to\\file.tar.gz").basename('\\') == "file.tar.gz");
1843 CHECK(ryml::csubstr("C:\\path\\to\\file.tar.gz").dirname('\\') == "C:\\path\\to\\");
1844 CHECK(ryml::csubstr("/path/to/file.tar.gz").extshort() == "gz");
1845 CHECK(ryml::csubstr("/path/to/file.tar.gz").extlong() == "tar.gz");
1846 CHECK(ryml::csubstr("/path/to/file.tar.gz").name_wo_extshort() == "/path/to/file.tar");
1847 CHECK(ryml::csubstr("/path/to/file.tar.gz").name_wo_extlong() == "/path/to/file");
1848 }
1849
1850 // split()
1851 {
1852 using namespace ryml;
1853 csubstr parts[] = {"aa", "bb", "cc", "dd", "ee", "ff"};
1854 {
1855 size_t count = 0;
1856 for(csubstr part : csubstr("aa/bb/cc/dd/ee/ff").split('/'))
1857 CHECK(part == parts[count++]);
1858 CHECK(count == 6u);
1859 }
1860 {
1861 size_t count = 0;
1862 for(csubstr part : csubstr("aa.bb.cc.dd.ee.ff").split('.'))
1863 CHECK(part == parts[count++]);
1864 CHECK(count == 6u);
1865 }
1866 {
1867 size_t count = 0;
1868 for(csubstr part : csubstr("aa-bb-cc-dd-ee-ff").split('-'))
1869 CHECK(part == parts[count++]);
1870 CHECK(count == 6u);
1871 }
1872 // see also next_split()
1873 }
1874
1875 // pop_left(), pop_right() --- non-greedy version
1876 // gpop_left(), gpop_right() --- greedy version
1877 {
1878 const bool skip_empty = true;
1879 // pop_left(): pop the last element from the left
1880 CHECK(ryml::csubstr( "0/1/2" ). pop_left('/' ) == "0" );
1881 CHECK(ryml::csubstr( "/0/1/2" ). pop_left('/' ) == "" );
1882 CHECK(ryml::csubstr("//0/1/2" ). pop_left('/' ) == "" );
1883 CHECK(ryml::csubstr( "0/1/2" ). pop_left('/', skip_empty) == "0" );
1884 CHECK(ryml::csubstr( "/0/1/2" ). pop_left('/', skip_empty) == "/0" );
1885 CHECK(ryml::csubstr("//0/1/2" ). pop_left('/', skip_empty) == "//0" );
1886 // gpop_left(): pop all but the first element (greedy pop)
1887 CHECK(ryml::csubstr( "0/1/2" ).gpop_left('/' ) == "0/1" );
1888 CHECK(ryml::csubstr( "/0/1/2" ).gpop_left('/' ) == "/0/1" );
1889 CHECK(ryml::csubstr("//0/1/2" ).gpop_left('/' ) == "//0/1" );
1890 CHECK(ryml::csubstr( "0/1/2/" ).gpop_left('/' ) == "0/1/2");
1891 CHECK(ryml::csubstr( "/0/1/2/" ).gpop_left('/' ) == "/0/1/2");
1892 CHECK(ryml::csubstr("//0/1/2/" ).gpop_left('/' ) == "//0/1/2");
1893 CHECK(ryml::csubstr( "0/1/2//" ).gpop_left('/' ) == "0/1/2/");
1894 CHECK(ryml::csubstr( "/0/1/2//" ).gpop_left('/' ) == "/0/1/2/");
1895 CHECK(ryml::csubstr("//0/1/2//" ).gpop_left('/' ) == "//0/1/2/");
1896 CHECK(ryml::csubstr( "0/1/2" ).gpop_left('/', skip_empty) == "0/1" );
1897 CHECK(ryml::csubstr( "/0/1/2" ).gpop_left('/', skip_empty) == "/0/1" );
1898 CHECK(ryml::csubstr("//0/1/2" ).gpop_left('/', skip_empty) == "//0/1" );
1899 CHECK(ryml::csubstr( "0/1/2/" ).gpop_left('/', skip_empty) == "0/1" );
1900 CHECK(ryml::csubstr( "/0/1/2/" ).gpop_left('/', skip_empty) == "/0/1" );
1901 CHECK(ryml::csubstr("//0/1/2/" ).gpop_left('/', skip_empty) == "//0/1" );
1902 CHECK(ryml::csubstr( "0/1/2//" ).gpop_left('/', skip_empty) == "0/1" );
1903 CHECK(ryml::csubstr( "/0/1/2//" ).gpop_left('/', skip_empty) == "/0/1" );
1904 CHECK(ryml::csubstr("//0/1/2//" ).gpop_left('/', skip_empty) == "//0/1" );
1905 // pop_right(): pop the last element from the right
1906 CHECK(ryml::csubstr( "0/1/2" ). pop_right('/' ) == "2" );
1907 CHECK(ryml::csubstr( "0/1/2/" ). pop_right('/' ) == "" );
1908 CHECK(ryml::csubstr( "0/1/2//" ). pop_right('/' ) == "" );
1909 CHECK(ryml::csubstr( "0/1/2" ). pop_right('/', skip_empty) == "2" );
1910 CHECK(ryml::csubstr( "0/1/2/" ). pop_right('/', skip_empty) == "2/" );
1911 CHECK(ryml::csubstr( "0/1/2//" ). pop_right('/', skip_empty) == "2//" );
1912 // gpop_right(): pop all but the first element (greedy pop)
1913 CHECK(ryml::csubstr( "0/1/2" ).gpop_right('/' ) == "1/2" );
1914 CHECK(ryml::csubstr( "0/1/2/" ).gpop_right('/' ) == "1/2/" );
1915 CHECK(ryml::csubstr( "0/1/2//" ).gpop_right('/' ) == "1/2//");
1916 CHECK(ryml::csubstr( "/0/1/2" ).gpop_right('/' ) == "0/1/2" );
1917 CHECK(ryml::csubstr( "/0/1/2/" ).gpop_right('/' ) == "0/1/2/" );
1918 CHECK(ryml::csubstr( "/0/1/2//" ).gpop_right('/' ) == "0/1/2//");
1919 CHECK(ryml::csubstr("//0/1/2" ).gpop_right('/' ) == "/0/1/2" );
1920 CHECK(ryml::csubstr("//0/1/2/" ).gpop_right('/' ) == "/0/1/2/" );
1921 CHECK(ryml::csubstr("//0/1/2//" ).gpop_right('/' ) == "/0/1/2//");
1922 CHECK(ryml::csubstr( "0/1/2" ).gpop_right('/', skip_empty) == "1/2" );
1923 CHECK(ryml::csubstr( "0/1/2/" ).gpop_right('/', skip_empty) == "1/2/" );
1924 CHECK(ryml::csubstr( "0/1/2//" ).gpop_right('/', skip_empty) == "1/2//");
1925 CHECK(ryml::csubstr( "/0/1/2" ).gpop_right('/', skip_empty) == "1/2" );
1926 CHECK(ryml::csubstr( "/0/1/2/" ).gpop_right('/', skip_empty) == "1/2/" );
1927 CHECK(ryml::csubstr( "/0/1/2//" ).gpop_right('/', skip_empty) == "1/2//");
1928 CHECK(ryml::csubstr("//0/1/2" ).gpop_right('/', skip_empty) == "1/2" );
1929 CHECK(ryml::csubstr("//0/1/2/" ).gpop_right('/', skip_empty) == "1/2/" );
1930 CHECK(ryml::csubstr("//0/1/2//" ).gpop_right('/', skip_empty) == "1/2//");
1931 }
1932}
1933
1934
1935//-----------------------------------------------------------------------------
1936
1937
1938/** demonstrate how to load a YAML file from disk to parse with ryml.
1939 *
1940 * ryml offers no overload to directly parse files from disk; it only
1941 * parses source buffers (which may be mutable or immutable). It is
1942 * up to the caller to first load the file contents into a buffer
1943 * before parsing with ryml. To help with this you can use the
1944 * (efficient) helper [file_get_contents](@ref
1945 * c4::yml::file_get_contents()). See also the analogous
1946 * [file_put_contents](@ref c4::yml::file_put_contents())
1947 *
1948 * @see doc_parse */
1950{
1951 const char filename[] = "ryml_example.yml";
1952 std::string yaml = ""
1953 "foo: 1" "\n"
1954 "bar:" "\n"
1955 "- 2" "\n"
1956 "- 3" "\n";
1957 // because this is a minimal sample, it assumes nothing on the
1958 // environment/OS (other than that it can read/write files). So we
1959 // create the file on the fly:
1960 ryml::file_put_contents(yaml, filename);
1961
1962 // now we can load it into a std::string (for example):
1963 {
1964 std::string contents = ryml::file_get_contents<std::string>(filename);
1965 ryml::Tree tree = ryml::parse_in_arena(ryml::to_csubstr(contents)); // immutable (csubstr) overload
1966 CHECK(tree["foo"].val() == "1");
1967 CHECK(tree["bar"][0].val() == "2");
1968 CHECK(tree["bar"][1].val() == "3");
1969 }
1970
1971 // or we can use a vector<char> instead:
1972 {
1973 std::vector<char> contents = ryml::file_get_contents<std::vector<char>>(filename);
1974 ryml::Tree tree = ryml::parse_in_place(ryml::to_substr(contents)); // mutable (substr) overload
1975 CHECK(tree["foo"].val() == "1");
1976 CHECK(tree["bar"][0].val() == "2");
1977 CHECK(tree["bar"][1].val() == "3");
1978 }
1979
1980 // generally, any contiguous char container can be used with ryml,
1981 // provided that the ryml::substr/ryml::csubstr view can be
1982 // created out of it.
1983 //
1984 // ryml provides the overloads above for these two containers, but
1985 // if you are using another container it should be very easy (only
1986 // requires pointer and length).
1987}
1988
1989
1990//-----------------------------------------------------------------------------
1991
1992/** demonstrate in-place parsing of a mutable YAML source buffer.
1993 * @see doc_parse */
1995{
1996 // Like the name suggests, parse_in_place() directly mutates the
1997 // source buffer in place
1998 char src[] = "{foo: 1, bar: [2, 3]}"; // ryml can parse in situ
1999 ryml::substr srcview = src; // a mutable view to the source buffer
2000 ryml::Tree tree = ryml::parse_in_place(srcview); // you can also reuse the tree and/or parser
2001 ryml::ConstNodeRef root = tree.crootref(); // get a constant reference to the root
2002
2003 CHECK(root.is_map());
2004 CHECK(root["foo"].is_keyval());
2005 CHECK(root["foo"].key() == "foo");
2006 CHECK(root["foo"].val() == "1");
2007 CHECK(root["bar"].is_seq());
2008 CHECK(root["bar"].has_key());
2009 CHECK(root["bar"].key() == "bar");
2010 CHECK(root["bar"][0].val() == "2");
2011 CHECK(root["bar"][1].val() == "3");
2012
2013 // deserializing:
2014 int foo = 0, bar0 = 0, bar1 = 0;
2015 root["foo"].load(&foo);
2016 root["bar"][0].load(&bar0);
2017 root["bar"][1].load(&bar1);
2018 CHECK(foo == 1);
2019 CHECK(bar0 == 2);
2020 CHECK(bar1 == 3);
2021
2022 // after parsing, the tree holds views to the source buffer:
2023 CHECK(root["foo"].val().data() == src + strlen("{foo: "));
2024 CHECK(root["foo"].val().begin() == src + strlen("{foo: "));
2025 CHECK(root["foo"].val().end() == src + strlen("{foo: 1"));
2026 CHECK(root["foo"].val().is_sub(srcview)); // equivalent to the previous three assertions
2027 CHECK(root["bar"][0].val().data() == src + strlen("{foo: 1, bar: ["));
2028 CHECK(root["bar"][0].val().begin() == src + strlen("{foo: 1, bar: ["));
2029 CHECK(root["bar"][0].val().end() == src + strlen("{foo: 1, bar: [2"));
2030 CHECK(root["bar"][0].val().is_sub(srcview)); // equivalent to the previous three assertions
2031 CHECK(root["bar"][1].val().data() == src + strlen("{foo: 1, bar: [2, "));
2032 CHECK(root["bar"][1].val().begin() == src + strlen("{foo: 1, bar: [2, "));
2033 CHECK(root["bar"][1].val().end() == src + strlen("{foo: 1, bar: [2, 3"));
2034 CHECK(root["bar"][1].val().is_sub(srcview)); // equivalent to the previous three assertions
2035
2036 // NOTE. parse_in_place() cannot accept ryml::csubstr
2037 // so this will cause a /compile/ error:
2038 ryml::csubstr csrcview = srcview; // ok, can assign from mutable to immutable
2039 //tree = ryml::parse_in_place(csrcview); // compile error, cannot mutate an immutable view
2040 (void)csrcview;
2041}
2042
2043
2044//-----------------------------------------------------------------------------
2045
2046/** demonstrate parsing of a read-only YAML source buffer
2047 * @see doc_parse */
2049{
2050 // to parse read-only memory, ryml will copy first to the tree's
2051 // arena, and then parse the copied buffer:
2052 ryml::Tree tree = ryml::parse_in_arena("{foo: 1, bar: [2, 3]}");
2053 ryml::ConstNodeRef root = tree.crootref(); // get a const reference to the root
2054
2055 CHECK(root.is_map());
2056 CHECK(root["foo"].is_keyval());
2057 CHECK(root["foo"].key() == "foo");
2058 CHECK(root["foo"].val() == "1");
2059 CHECK(root["bar"].is_seq());
2060 CHECK(root["bar"].has_key());
2061 CHECK(root["bar"].key() == "bar");
2062 CHECK(root["bar"][0].val() == "2");
2063 CHECK(root["bar"][1].val() == "3");
2064
2065 // deserializing:
2066 int foo = 0, bar0 = 0, bar1 = 0;
2067 root["foo"].load(&foo);
2068 root["bar"][0].load(&bar0);
2069 root["bar"][1].load(&bar1);
2070 CHECK(foo == 1);
2071 CHECK(bar0 == 2);
2072 CHECK(bar1 == 3);
2073
2074 // NOTE. parse_in_arena() cannot accept ryml::substr. Overloads
2075 // receiving substr buffers are declared, but intentionally left
2076 // undefined, so this will cause a /linker/ error
2077 char src[] = "{foo: is it really true}";
2078 ryml::substr srcview = src;
2079 //tree = ryml::parse_in_place(srcview); // linker error, overload intentionally undefined
2080
2081 // If you really intend to parse a mutable buffer in the arena,
2082 // then simply convert it to immutable prior to calling
2083 // parse_in_arena():
2084 ryml::csubstr csrcview = srcview; // assigning from src also works
2085 tree = ryml::parse_in_arena(csrcview); // OK! csrcview is immutable
2086 CHECK(tree["foo"].val() == "is it really true");
2087}
2088
2089
2090//-----------------------------------------------------------------------------
2091
2092/** demonstrate reuse/modification of tree when parsing
2093 * @see doc_parse */
2095{
2096 ryml::Tree tree;
2097
2098 // it will always be faster if the tree's size is conveniently reserved:
2099 tree.reserve(30); // reserve 30 nodes (good enough for this sample)
2100 // if you are using the tree's arena to serialize data,
2101 // then reserve also the arena's size:
2102 tree.reserve_arena(256); // reserve 256 characters (good enough for this sample)
2103
2104 // now parse into the tree:
2105 ryml::csubstr yaml = ""
2106 "foo: 1" "\n"
2107 "bar: [2,3]" "\n"
2108 "";
2109 ryml::parse_in_arena(yaml, &tree);
2110
2111 ryml::ConstNodeRef root = tree.crootref();
2112 CHECK(root.num_children() == 2);
2113 CHECK(root.is_map());
2114 CHECK(root["foo"].is_keyval());
2115 CHECK(root["foo"].key() == "foo");
2116 CHECK(root["foo"].val() == "1");
2117 CHECK(root["bar"].is_seq());
2118 CHECK(root["bar"].has_key());
2119 CHECK(root["bar"].key() == "bar");
2120 CHECK(root["bar"][0].val() == "2");
2121 CHECK(root["bar"][1].val() == "3");
2123
2124 // WATCHOUT: parsing into an existing tree will APPEND to it:
2125 ryml::parse_in_arena("{foo2: 12, bar2: [22, 32]}", &tree);
2127 "foo: 1" "\n"
2128 "bar: [2,3]" "\n"
2129 "foo2: 12" "\n"
2130 "bar2: [22,32]" "\n"
2131 "");
2132 CHECK(root.num_children() == 4);
2133 CHECK(root["foo2"].is_keyval());
2134 CHECK(root["foo2"].key() == "foo2");
2135 CHECK(root["foo2"].val() == "12");
2136 CHECK(root["bar2"].is_seq());
2137 CHECK(root["bar2"].has_key());
2138 CHECK(root["bar2"].key() == "bar2");
2139 CHECK(root["bar2"][0].val() == "22");
2140 CHECK(root["bar2"][1].val() == "32");
2141
2142 // if you want to fully replace the tree, you need to clear first
2143 // before parsing into an existing tree:
2144 tree.clear();
2145 tree.clear_arena(); // you may or may not want to clear the arena
2146 ryml::parse_in_arena("- a\n- b\n- {x0: 1, x1: 2}", &tree);
2147 CHECK(ryml::emitrs_yaml<std::string>(tree) == "- a\n- b\n- {x0: 1,x1: 2}\n");
2148 CHECK(root.is_seq());
2149 CHECK(root[0].val() == "a");
2150 CHECK(root[1].val() == "b");
2151 CHECK(root[2].is_map());
2152 CHECK(root[2]["x0"].val() == "1");
2153 CHECK(root[2]["x1"].val() == "2");
2154
2155 // we can parse directly into a node nested deep in an existing tree:
2156 ryml::NodeRef mroot = tree.rootref(); // modifiable root
2157 ryml::parse_in_arena("champagne: Dom Perignon\ncoffee: Arabica", mroot.append_child());
2159 "- a" "\n"
2160 "- b" "\n"
2161 "- {x0: 1,x1: 2}" "\n"
2162 "- champagne: Dom Perignon" "\n"
2163 " coffee: Arabica" "\n"
2164 "");
2165 CHECK(root.is_seq());
2166 CHECK(root[0].val() == "a");
2167 CHECK(root[1].val() == "b");
2168 CHECK(root[2].is_map());
2169 CHECK(root[2]["x0"].val() == "1");
2170 CHECK(root[2]["x1"].val() == "2");
2171 CHECK(root[3].is_map());
2172 CHECK(root[3]["champagne"].val() == "Dom Perignon");
2173 CHECK(root[3]["coffee"].val() == "Arabica");
2174
2175 mroot[3]["more"].set_map();
2176 mroot[3]["beer"].set_seq();
2177 CHECK(mroot[3]["more"].readable());
2178 CHECK(mroot[3]["more"].key() == "more");
2179 CHECK(mroot[3]["more"].is_map());
2180 CHECK(!mroot[3]["more"].is_val());
2181 ryml::parse_in_arena("{vinho verde: Soalheiro, vinho tinto: Redoma 2017}", mroot[3]["more"]);
2182 ryml::parse_in_arena("- Rochefort 10\n- Busch\n- Leffe Rituel", mroot[3]["beer"]);
2183 ryml::parse_in_arena("lots\nof\nwater", mroot[3]["always"]);
2185 "- a" "\n"
2186 "- b" "\n"
2187 "- {x0: 1,x1: 2}" "\n"
2188 "- champagne: Dom Perignon" "\n"
2189 " coffee: Arabica" "\n"
2190 // note these were added:
2191 " more:" "\n"
2192 " vinho verde: Soalheiro" "\n"
2193 " vinho tinto: Redoma 2017" "\n"
2194 " beer:" "\n"
2195 " - Rochefort 10" "\n"
2196 " - Busch" "\n"
2197 " - Leffe Rituel" "\n"
2198 " always: lots of water" "\n"
2199 "");
2200
2201 // can append at the top:
2202 ryml::parse_in_arena("- foo\n- bar\n- baz\n- bat", mroot);
2204 "- a" "\n"
2205 "- b" "\n"
2206 "- {x0: 1,x1: 2}" "\n"
2207 "- champagne: Dom Perignon" "\n"
2208 " coffee: Arabica" "\n"
2209 " more:" "\n"
2210 " vinho verde: Soalheiro" "\n"
2211 " vinho tinto: Redoma 2017" "\n"
2212 " beer:" "\n"
2213 " - Rochefort 10" "\n"
2214 " - Busch" "\n"
2215 " - Leffe Rituel" "\n"
2216 " always: lots of water" "\n"
2217 // note these were added
2218 "- foo" "\n"
2219 "- bar" "\n"
2220 "- baz" "\n"
2221 "- bat" "\n"
2222 "");
2223
2224 // or nested:
2225 ryml::parse_in_arena("[Kasteel Donker]", mroot[3]["beer"]);
2227 "- a" "\n"
2228 "- b" "\n"
2229 "- {x0: 1,x1: 2}" "\n"
2230 "- champagne: Dom Perignon" "\n"
2231 " coffee: Arabica" "\n"
2232 " more:" "\n"
2233 " vinho verde: Soalheiro" "\n"
2234 " vinho tinto: Redoma 2017" "\n"
2235 " beer:" "\n"
2236 " - Rochefort 10" "\n"
2237 " - Busch" "\n"
2238 " - Leffe Rituel" "\n"
2239 " - Kasteel Donker" "\n" // added this
2240 " always: lots of water" "\n"
2241 "- foo" "\n"
2242 "- bar" "\n"
2243 "- baz" "\n"
2244 "- bat" "\n"
2245 "");
2246}
2247
2248
2249//-----------------------------------------------------------------------------
2250
2251/** Demonstrates reuse of an existing parser. Doing this is
2252 * recommended when multiple files are parsed.
2253 * @see doc_parse */
2255{
2256 ryml::EventHandlerTree evt_handler = {};
2257 ryml::Parser parser(&evt_handler);
2258
2259 // it is also advised to reserve the parser depth
2260 // to the expected depth of the data tree:
2261 parser.reserve_stack(10); // uses small storage optimization
2262 // defaulting to 16 depth, so this
2263 // instruction is a no-op, and the stack
2264 // will located in the parser object.
2265 parser.reserve_stack(20); // But this will cause an allocation
2266 // because it is above 16.
2267
2268 ryml::csubstr yaml = "[Dom Perignon,Gosset Grande Reserve,Jacquesson 742]";
2269 ryml::Tree champagnes = parse_in_arena(&parser, "champagnes.yml", yaml);
2270 CHECK(ryml::emitrs_yaml<std::string>(champagnes) == yaml);
2271
2272 yaml = "[Rochefort 10,Busch,Leffe Rituel,Kasteel Donker]";
2273 ryml::Tree beers = parse_in_arena(&parser, "beers.yml", yaml);
2274 CHECK(ryml::emitrs_yaml<std::string>(beers) == yaml);
2275}
2276
2277
2278//-----------------------------------------------------------------------------
2279
2280/** for ultimate speed when parsing multiple times, reuse both the
2281 * tree and parser
2282 * @see doc_parse */
2284{
2285 ryml::Tree tree;
2286 // it will always be faster if the tree's size is conveniently reserved:
2287 tree.reserve(30); // reserve 30 nodes (good enough for this sample)
2288 // if you are using the tree's arena to serialize data,
2289 // then reserve also the arena's size:
2290 tree.reserve(256); // reserve 256 characters (good enough for this sample)
2291
2292 ryml::EventHandlerTree evt_handler;
2293 ryml::Parser parser(&evt_handler);
2294 // it is also advised to reserve the parser depth
2295 // to the expected depth of the data tree:
2296 parser.reserve_stack(10); // the parser uses small storage
2297 // optimization defaulting to 16 depth,
2298 // so this instruction is a no-op, and
2299 // the stack will be located in the
2300 // parser object.
2301 parser.reserve_stack(20); // But this will cause an allocation
2302 // because it is above 16.
2303
2304 ryml::csubstr champagnes = ""
2305 "- Dom Perignon\n"
2306 "- Gosset Grande Reserve\n"
2307 "- Jacquesson 742\n"
2308 "";
2309 ryml::csubstr beers = ""
2310 "- Rochefort 10\n"
2311 "- Busch\n"
2312 "- Leffe Rituel\n"
2313 "- Kasteel Donker\n"
2314 "";
2315 ryml::csubstr wines = ""
2316 "- Soalheiro\n"
2317 "- Niepoort Redoma 2017\n"
2318 "- Vina Esmeralda\n"
2319 "";
2320
2321 parse_in_arena(&parser, "champagnes.yml", champagnes, &tree);
2322 CHECK(ryml::emitrs_yaml<std::string>(tree) == champagnes);
2323
2324 // watchout: this will APPEND to the given tree:
2325 parse_in_arena(&parser, "beers.yml", beers, &tree);
2327 "- Dom Perignon\n"
2328 "- Gosset Grande Reserve\n"
2329 "- Jacquesson 742\n"
2330 "- Rochefort 10\n"
2331 "- Busch\n"
2332 "- Leffe Rituel\n"
2333 "- Kasteel Donker\n"
2334 "");
2335
2336 // if you don't wish to append, clear the tree first:
2337 tree.clear();
2338 parse_in_arena(&parser, "wines.yml", wines, &tree);
2340 "- Soalheiro\n"
2341 "- Niepoort Redoma 2017\n"
2342 "- Vina Esmeralda\n"
2343 "");
2344}
2345
2346
2347//-----------------------------------------------------------------------------
2348
2349/** shows how rapidyaml marks the tree nodes with their original style
2350 * in the parsed YAML.
2351 *
2352 * @see doc_tree
2353 * @see doc_node_classes
2354 */
2356{
2357 ryml::csubstr yaml = ""
2358 "doe: a deer, a female deer" "\n"
2359 "ray: 'a drop of golden sun'" "\n"
2360 "me: \"a name I call myself\"" "\n"
2361 "far: |-" "\n"
2362 " a long long way to go" "\n"
2363 "sow: >-" "\n"
2364 " a needle pulling thread" "\n"
2365 "seq: [0,1,2,3]" "\n"
2366 "map: {" "\n"
2367 " 0: 10," "\n"
2368 " 1: 11," "\n"
2369 " 2: 12," "\n"
2370 " 3: 13" "\n"
2371 " }" "\n"
2372 "";
2373 const ryml::Tree tree = ryml::parse_in_arena(yaml);
2374 // note how every node is tagged with its original style:
2375 CHECK(tree["doe"].is_val_plain());
2376 CHECK(tree["ray"].is_val_squo());
2377 CHECK(tree["me"].is_val_dquo());
2378 CHECK(tree["far"].is_val_literal());
2379 CHECK(tree["sow"].is_val_folded());
2380 CHECK(tree["seq"].is_flow());
2381 CHECK(tree["seq"].is_flow_sl());
2382 CHECK(tree["map"].is_flow());
2383 CHECK(tree["map"].is_flow_mlx());
2384 CHECK(tree.rootref().is_block());
2385 // ... which results in roundtrip-stable YAML:
2387}
2388
2389
2390//-----------------------------------------------------------------------------
2391
2392/** shows how to programatically iterate through trees
2393 * @see doc_tree
2394 * @see doc_node_classes
2395 */
2397{
2398 const ryml::Tree tree = ryml::parse_in_arena(
2399 "doe: a deer, a female deer" "\n"
2400 "ray: a drop of golden sun" "\n"
2401 "pi: 3.14159" "\n"
2402 "xmas: true" "\n"
2403 "french-hens: 3" "\n"
2404 "calling-birds:" "\n"
2405 " - huey" "\n"
2406 " - dewey" "\n"
2407 " - louie" "\n"
2408 " - fred" "\n"
2409 "xmas-fifth-day:" "\n"
2410 " calling-birds: four" "\n"
2411 " french-hens: 3" "\n"
2412 " golden-rings: 5" "\n"
2413 " partridges:" "\n"
2414 " count: 1" "\n"
2415 " location: a pear tree" "\n"
2416 " turtle-doves: two" "\n"
2417 "cars: GTO" "\n"
2418 "");
2419 ryml::ConstNodeRef root = tree.crootref();
2420
2421 // iterate children
2422 {
2423 std::vector<ryml::csubstr> keys, vals; // to store all the root-level keys, vals
2424 for(ryml::ConstNodeRef n : root.children())
2425 {
2426 keys.emplace_back(n.key());
2427 vals.emplace_back(n.has_val() ? n.val() : ryml::csubstr{});
2428 }
2429 CHECK(keys.size() >= 6);
2430 CHECK(vals.size() >= 6);
2431 if(keys.size() >= 6 && vals.size() >= 6)
2432 {
2433 CHECK(keys[0] == "doe");
2434 CHECK(vals[0] == "a deer, a female deer");
2435 CHECK(keys[1] == "ray");
2436 CHECK(vals[1] == "a drop of golden sun");
2437 CHECK(keys[2] == "pi");
2438 CHECK(vals[2] == "3.14159");
2439 CHECK(keys[3] == "xmas");
2440 CHECK(vals[3] == "true");
2441 CHECK(root[5].has_key());
2442 CHECK(root[5].is_seq());
2443 CHECK(root[5].key() == "calling-birds");
2444 CHECK(!root[5].has_val()); // it is a map, so not a val
2445 //CHECK(root[5].val() == ""); // ERROR! node does not have a val.
2446 CHECK(keys[5] == "calling-birds");
2447 CHECK(vals[5] == "");
2448 }
2449 }
2450
2451 // iterate siblings
2452 {
2453 size_t count = 0;
2454 ryml::csubstr calling_birds[] = {"huey", "dewey", "louie", "fred"};
2455 for(ryml::ConstNodeRef n : root["calling-birds"][2].siblings())
2456 CHECK(n.val() == calling_birds[count++]);
2457 CHECK(count == 4u);
2458 }
2459}
2460
2461
2462//-----------------------------------------------------------------------------
2463
2464/** demonstrates how to obtain the (zero-based) location of a node
2465 * from a recently parsed tree
2466 *
2467 * @see RYML_LOCATIONS_SMALL_THRESHOLD
2468 * */
2470{
2471 // NOTE: locations are zero-based. If you intend to show the
2472 // location to a human user, you may want to pre-increment the line
2473 // and column by 1.
2474 ryml::csubstr yaml = ""
2475 "{" "\n"
2476 "aa: contents," "\n"
2477 "foo: [one, [two, three]]" "\n"
2478 "}" "\n"
2479 "";
2480 // A parser is needed to track locations, and it has to be
2481 // explicitly set to do it. Location tracking is disabled by
2482 // default.
2483 ryml::ParserOptions opts = {};
2484 opts.locations(true); // enable locations, default is false
2485 ryml::EventHandlerTree evt_handler = {};
2486 ryml::Parser parser(&evt_handler, opts);
2487 CHECK(parser.options().locations());
2488 // When locations are enabled, the first task while parsing will
2489 // consist of building and caching (in the parser) a
2490 // source-to-node lookup structure to accelerate location lookups.
2491 //
2492 // The cost of building the location accelerator is linear in the
2493 // size of the source buffer. This increased cost is the reason
2494 // for the opt-in requirement. When locations are disabled there
2495 // is no cost.
2496 //
2497 // Building the location accelerator may trigger an allocation,
2498 // but this can and should be avoided by reserving prior to
2499 // parsing:
2500 parser.reserve_locations(50u); // reserve for 50 lines of YAML code
2501 // Now the structure will be built during parsing:
2502 ryml::Tree tree = parse_in_arena(&parser, "source.yml", yaml);
2503 // After this, we are ready to query the location from the parser:
2504 ryml::Location loc = tree.rootref().location(parser);
2505 // As for the complexity of the query: for large buffers it is
2506 // O(log(numlines)). For short source buffers
2507 // (RYML_LOCATIONS_SMALL_THRESHOLD lines and less), it is
2508 // O(numlines), as a plain linear search is faster in this case.
2509 CHECK(parser.location_contents(loc).begins_with("{"));
2510 CHECK(loc.offset == 0u);
2511 CHECK(loc.line == 0u);
2512 CHECK(loc.col == 0u);
2513 // on the next call, we only pay O(log(numlines)) because the
2514 // rebuild is already available:
2515 loc = tree["aa"].location(parser);
2516 CHECK(parser.location_contents(loc).begins_with("aa"));
2517 CHECK(loc.offset == 2u);
2518 CHECK(loc.line == 1u);
2519 CHECK(loc.col == 0u);
2520 // KEYSEQ in flow style: points at the key
2521 loc = tree["foo"].location(parser);
2522 CHECK(parser.location_contents(loc).begins_with("foo"));
2523 CHECK(loc.offset == 16u);
2524 CHECK(loc.line == 2u);
2525 CHECK(loc.col == 0u);
2526 loc = tree["foo"][0].location(parser);
2527 CHECK(parser.location_contents(loc).begins_with("one"));
2528 CHECK(loc.line == 2u);
2529 CHECK(loc.col == 6u);
2530 // SEQ in flow style: location points at the opening '[' (there's no key)
2531 loc = tree["foo"][1].location(parser);
2532 CHECK(parser.location_contents(loc).begins_with("["));
2533 CHECK(loc.line == 2u);
2534 CHECK(loc.col == 11u);
2535 loc = tree["foo"][1][0].location(parser);
2536 CHECK(parser.location_contents(loc).begins_with("two"));
2537 CHECK(loc.line == 2u);
2538 CHECK(loc.col == 12u);
2539 loc = tree["foo"][1][1].location(parser);
2540 CHECK(parser.location_contents(loc).begins_with("three"));
2541 CHECK(loc.line == 2u);
2542 CHECK(loc.col == 17u);
2543 // NOTE. The parser locations always point at the latest buffer to
2544 // be parsed with the parser object, so they must be queried using
2545 // the corresponding latest tree to be parsed. This means that if
2546 // the parser is reused, earlier trees will loose the possibility
2547 // of querying for location. It is undefined behavior to query the
2548 // parser for the location of a node from an earlier tree:
2549 ryml::Tree docval = parse_in_arena(&parser, "docval.yaml", "this is a docval");
2550 // From now on, none of the locations from the previous tree can
2551 // be queried:
2552 //loc = tree.rootref().location(parser); // ERROR, undefined behavior
2553 loc = docval.rootref().location(parser); // OK. this is the latest tree from this parser
2554 CHECK(parser.location_contents(loc).begins_with("this is a docval"));
2555 CHECK(loc.line == 0u);
2556 CHECK(loc.col == 0u);
2557
2558 // NOTES ABOUT CONTAINER LOCATIONS
2559 ryml::Tree tree2 = parse_in_arena(&parser, "containers.yaml",
2560 "" "\n"
2561 "a new: buffer" "\n"
2562 "to: be parsed" "\n"
2563 "map with key:" "\n"
2564 " first: value" "\n"
2565 " second: value" "\n"
2566 "seq with key:" "\n"
2567 " - first value" "\n"
2568 " - second value" "\n"
2569 " -" "\n"
2570 " - nested first value" "\n"
2571 " - nested second value" "\n"
2572 " -" "\n"
2573 " nested first: value" "\n"
2574 " nested second: value" "\n"
2575 "");
2576 // (Likewise, the docval tree can no longer be used to query.)
2577 //
2578 // For key-less block-style maps, the location of the container
2579 // points at the first child's key. For example, in this case
2580 // the root does not have a key, so its location is taken
2581 // to be at the first child:
2582 loc = tree2.rootref().location(parser);
2583 CHECK(parser.location_contents(loc).begins_with("a new"));
2584 CHECK(loc.offset == 1u);
2585 CHECK(loc.line == 1u);
2586 CHECK(loc.col == 0u);
2587 // note the first child points exactly at the same place:
2588 loc = tree2["a new"].location(parser);
2589 CHECK(parser.location_contents(loc).begins_with("a new"));
2590 CHECK(loc.offset == 1u);
2591 CHECK(loc.line == 1u);
2592 CHECK(loc.col == 0u);
2593 loc = tree2["to"].location(parser);
2594 CHECK(parser.location_contents(loc).begins_with("to"));
2595 CHECK(loc.line == 2u);
2596 CHECK(loc.col == 0u);
2597 // but of course, if the block-style map is a KEYMAP, then the
2598 // location is the map's key, and not the first child's key:
2599 loc = tree2["map with key"].location(parser);
2600 CHECK(parser.location_contents(loc).begins_with("map with key"));
2601 CHECK(loc.line == 3u);
2602 CHECK(loc.col == 0u);
2603 loc = tree2["map with key"]["first"].location(parser);
2604 CHECK(parser.location_contents(loc).begins_with("first"));
2605 CHECK(loc.line == 4u);
2606 CHECK(loc.col == 2u);
2607 loc = tree2["map with key"]["second"].location(parser);
2608 CHECK(parser.location_contents(loc).begins_with("second"));
2609 CHECK(loc.line == 5u);
2610 CHECK(loc.col == 2u);
2611 // same thing for KEYSEQ:
2612 loc = tree2["seq with key"].location(parser);
2613 CHECK(parser.location_contents(loc).begins_with("seq with key"));
2614 CHECK(loc.line == 6u);
2615 CHECK(loc.col == 0u);
2616 loc = tree2["seq with key"][0].location(parser);
2617 CHECK(parser.location_contents(loc).begins_with("first value"));
2618 CHECK(loc.line == 7u);
2619 CHECK(loc.col == 4u);
2620 loc = tree2["seq with key"][1].location(parser);
2621 CHECK(parser.location_contents(loc).begins_with("second value"));
2622 CHECK(loc.line == 8u);
2623 CHECK(loc.col == 4u);
2624 // SEQ nested in SEQ: container location points at the first child's "- " dash
2625 loc = tree2["seq with key"][2].location(parser);
2626 CHECK(parser.location_contents(loc).begins_with("- nested first value"));
2627 CHECK(loc.line == 10u);
2628 CHECK(loc.col == 4u);
2629 loc = tree2["seq with key"][2][0].location(parser);
2630 CHECK(parser.location_contents(loc).begins_with("nested first value"));
2631 CHECK(loc.line == 10u);
2632 CHECK(loc.col == 6u);
2633 // MAP nested in SEQ: same as above: point to key
2634 loc = tree2["seq with key"][3].location(parser);
2635 CHECK(parser.location_contents(loc).begins_with("nested first: "));
2636 CHECK(loc.line == 13u);
2637 CHECK(loc.col == 4u);
2638 loc = tree2["seq with key"][3][0].location(parser);
2639 CHECK(parser.location_contents(loc).begins_with("nested first: "));
2640 CHECK(loc.line == 13u);
2641 CHECK(loc.col == 4u);
2642}
2643
2644
2645//-----------------------------------------------------------------------------
2646
2647/** shows how to programatically create trees
2648 * @see doc_tree
2649 * @see doc_node_classes
2650 * @see sample_create_tree_style()
2651 * */
2653{
2654 ryml::NodeRef doe;
2655 CHECK(doe.invalid()); // it's pointing at nowhere
2656
2657 ryml::Tree tree;
2658 ryml::NodeRef root = tree.rootref();
2659 root.set_map(); // mark root as a map
2660 doe = root["doe"];
2661 CHECK(!doe.invalid()); // it's now pointing at the tree
2662 CHECK(doe.is_seed()); // but the tree has nothing there, so this is only a seed
2663
2664 // set the value of the node
2665 const char a_deer[] = "a deer, a female deer";
2666 doe.set_val(a_deer);
2667 // now the node really exists in the tree, and this ref is no
2668 // longer a seed:
2669 CHECK(!doe.is_seed());
2670 // WATCHOUT for lifetimes:
2671 CHECK(doe.val().str == a_deer); // it is pointing at the initial string
2672 // If you need to avoid lifetime dependency, serialize the data:
2673 {
2674 std::string a_drop = "a drop of golden sun";
2675 // this will copy the string to the tree's arena:
2676 // (see the serialization samples below)
2677 root["ray"].set_serialized(a_drop);
2678 // and now you can modify the original string without changing
2679 // the tree:
2680 a_drop[0] = 'Z';
2681 a_drop[1] = 'Z';
2682 }
2683 CHECK(root["ray"].val() == "a drop of golden sun");
2684
2685 // etc.
2686 root["pi"].set_serialized(ryml::fmt::real(3.141592654, 5));
2687 root["xmas"].set_serialized(ryml::fmt::boolalpha(true));
2688 root["french-hens"].set_serialized(3);
2689 ryml::NodeRef calling_birds = root["calling-birds"];
2690 calling_birds.set_seq();
2691 calling_birds.append_child().set_val("huey");
2692 calling_birds.append_child().set_val("dewey");
2693 calling_birds.append_child().set_val("louie");
2694 calling_birds.append_child().set_val("fred");
2695 ryml::NodeRef xmas5 = root["xmas-fifth-day"];
2696 xmas5.set_map();
2697 xmas5["calling-birds"].set_val("four");
2698 xmas5["french-hens"].set_serialized(3);
2699 xmas5["golden-rings"].set_serialized(5);
2700 xmas5["partridges"].set_map();
2701 xmas5["partridges"]["count"].set_serialized(1);
2702 xmas5["partridges"]["location"].set_val("a pear tree");
2703 xmas5["turtle-doves"].set_val("two");
2704 root["cars"].set_val("GTO");
2705
2707 "doe: a deer, a female deer" "\n"
2708 "ray: a drop of golden sun" "\n"
2709 "pi: 3.14159" "\n"
2710 "xmas: true" "\n"
2711 "french-hens: 3" "\n"
2712 "calling-birds:" "\n"
2713 " - huey" "\n"
2714 " - dewey" "\n"
2715 " - louie" "\n"
2716 " - fred" "\n"
2717 "xmas-fifth-day:" "\n"
2718 " calling-birds: four" "\n"
2719 " french-hens: 3" "\n"
2720 " golden-rings: 5" "\n"
2721 " partridges:" "\n"
2722 " count: 1" "\n"
2723 " location: a pear tree" "\n"
2724 " turtle-doves: two" "\n"
2725 "cars: GTO" "\n"
2726 "");
2727
2728 // NOTE: it is good practice to set scalar styles when building
2729 // the tree. See the sample_create_tree_style() below.
2730}
2731
2732
2733//-----------------------------------------------------------------------------
2734
2735/** Shows how to set styles when building a tree: this may be needed
2736 * because you want to control the emitted YAML, but for scalars there
2737 * is also a performance reason to do this.
2738 *
2739 * @note You can **and should** set the style when creating a tree for
2740 * emitting later. This is because YAML has constraints on which
2741 * styles can be used for a particular scalar (eg, a plain scalar have
2742 * leading or trailing whitespace, or if in flow mode it cannot have
2743 * comma followed by space).
2744 *
2745 * @note When the scalar is not marked with an explicit style, the
2746 * ryml emitter adheres to these constraints by scanning each scalar
2747 * to choose a style for it. On the other hand, if the scalar is
2748 * marked with an explicit style, the emitter will honor that style,
2749 * and not do the scan.
2750 *
2751 * @note So explicitly setting the style for scalars
2752 * saves the emitter from having to scan each scalar while
2753 * emitting.
2754 *
2755 * @note For containers, setting the style does not offer an emit
2756 * performance improvement like with scalars. Nevertheless, you may
2757 * still find it useful to control the emitted YAML.
2758 *
2759 * Following this recommendation, ryml also explicitly sets the styles
2760 * while parsing.
2761 */
2763{
2764 ryml::Tree tree;
2765 ryml::NodeRef root = tree;
2766 root.set_map(ryml::BLOCK);
2767 // with .set_*() we should use explicit styles (because .set_*()
2768 // only takes csubstr arguments)
2769 root["not plain"].set_val(" with whitespace "); // no style set
2770 root["doe"].set_val("a deer, a female deer", ryml::VAL_PLAIN);
2771 root["ray"].set_val("a drop of golden sun", ryml::VAL_SQUO);
2772 root["me"].set_val("a name I call myself", ryml::VAL_DQUO);
2773 root["far"].set_val("a long long way to go", ryml::VAL_LITERAL);
2774 root["sow"].set_val("a needle pulling thread", ryml::VAL_FOLDED);
2775 root["seq"].set_seq(ryml::FLOW_SL|ryml::FLOW_SPC); // flow, single-line, with spaces after commas
2776 root["map1"].set_map(ryml::FLOW_ML1); // flow, multiline, 1 value per line
2777 root["mapn"].set_map(ryml::FLOW_MLN); // flow, multiline, N values per line, wrapped
2778 // likewise for all of set_serialized(), set_key(),
2779 // set_key_serialized(), save(), save_key(): all these accept the
2780 // style as an extra argument. But when serializing there's a
2781 // nice feature: ryml will automatically set the scalar style to
2782 // VAL_PLAIN / KEY_PLAIN when its type verifies std::is_arithmetic<T>.
2783 for(int i : {0, 1, 2, 3})
2784 {
2785 ryml::NodeRef childseq = root["seq"].append_child();
2786 ryml::NodeRef childmap1 = root["map1"].append_child();
2787 ryml::NodeRef childmapn = root["mapn"].append_child();
2788 // Note how we're NOT setting the style:
2789 childseq.set_serialized(i);
2790 childmap1.set_key_serialized(i + 1);
2791 childmapn.set_key_serialized(i + 1);
2792 childmap1.set_serialized((i + 1) * 10);
2793 childmapn.set_serialized((i + 1) * 10);
2794 // ... and yet ryml has set it to plain:
2795 CHECK(childseq.is_val_plain());
2796 CHECK(childmap1.is_key_plain());
2797 CHECK(childmapn.is_key_plain());
2798 CHECK(childmap1.is_val_plain());
2799 CHECK(childmapn.is_val_plain());
2800 }
2801 // let's see the styles now:
2803 // note how this is quoted, without having explicit style
2804 // set. That is because plain scalars cannot have
2805 // leading/trailing whitespace characters. This scalar did
2806 // not have a style set, so during emitting it was scanned
2807 // to determine its style, and so VAL_SQUO was chosen:
2808 "not plain: ' with whitespace '" "\n"
2809 // as for the rest, the style is honored, and the scalars
2810 // are not scanned by the emitter, resulting in a
2811 // performance increase:
2812 "doe: a deer, a female deer" "\n"
2813 "ray: 'a drop of golden sun'" "\n"
2814 "me: \"a name I call myself\"" "\n"
2815 "far: |-" "\n"
2816 " a long long way to go" "\n"
2817 "sow: >-" "\n"
2818 " a needle pulling thread" "\n"
2819 "seq: [0, 1, 2, 3]" "\n"
2820 "map1: {" "\n"
2821 " 1: 10," "\n"
2822 " 2: 20," "\n"
2823 " 3: 30," "\n"
2824 " 4: 40" "\n"
2825 " }" "\n"
2826 "mapn: {" "\n"
2827 " 1: 10,2: 20,3: 30,4: 40" "\n"
2828 " }" "\n"
2829 "");
2830 // Note that it would be an error to set a scalar style
2831 // incompatible with the scalar contents. ryml always honor the
2832 // node style, so it will blindly emit roundtrip-unstable YAML if
2833 // the style is incompatible.
2834 //
2835 // For example, this is incorrect because a plain scalar cannot
2836 // have leading or trailing whitespace:
2837 root["plain"].set_val(" with whitespace ", ryml::VAL_PLAIN); // incorrect.
2838 // note how the whitespace will be lost when parsed:
2839 CHECK(ryml::emitrs_yaml<std::string>(root["plain"]) ==
2840 "plain: with whitespace " "\n"
2841 "");
2842}
2843
2844
2845//-----------------------------------------------------------------------------
2846
2847/** demonstrates explicit and implicit interaction with the tree's
2848 * string arena. Note that ryml only holds strings in the tree's
2849 * nodes. */
2851{
2852 // mutable buffers are parsed in place:
2853 {
2854 char buf[] = "[a, b, c, d]";
2855 ryml::substr yml = buf;
2857 // notice the arena is empty:
2858 CHECK(tree.arena().empty());
2859 // and the tree is pointing at the original buffer:
2860 ryml::NodeRef root = tree.rootref();
2861 CHECK(root[0].val().is_sub(yml));
2862 CHECK(root[1].val().is_sub(yml));
2863 CHECK(root[2].val().is_sub(yml));
2864 CHECK(root[3].val().is_sub(yml));
2865 CHECK(yml.is_super(root[0].val()));
2866 CHECK(yml.is_super(root[1].val()));
2867 CHECK(yml.is_super(root[2].val()));
2868 CHECK(yml.is_super(root[3].val()));
2869 }
2870
2871 // when parsing immutable buffers, the buffer is first copied to the
2872 // tree's arena; the copy in the arena is then the buffer which is
2873 // actually parsed
2874 {
2875 ryml::csubstr yml = "[a, b, c, d]";
2877 // notice the buffer was copied to the arena:
2878 CHECK(tree.arena().data() != yml.data());
2879 CHECK(tree.arena() == yml);
2880 // and the tree is pointing at the arena instead of to the
2881 // original buffer:
2882 ryml::NodeRef root = tree.rootref();
2883 ryml::csubstr arena = tree.arena();
2884 CHECK(root[0].val().is_sub(arena));
2885 CHECK(root[1].val().is_sub(arena));
2886 CHECK(root[2].val().is_sub(arena));
2887 CHECK(root[3].val().is_sub(arena));
2888 CHECK(arena.is_super(root[0].val()));
2889 CHECK(arena.is_super(root[1].val()));
2890 CHECK(arena.is_super(root[2].val()));
2891 CHECK(arena.is_super(root[3].val()));
2892 }
2893
2894 // the arena is also used when the data is serialized to the tree.
2895 // first example: parse in place
2896 {
2897 char buf[] = "[a, b, c, d]"; // mutable
2898 ryml::substr yml = buf;
2900 // notice the arena is empty:
2901 CHECK(tree.arena().empty());
2902 ryml::NodeRef root = tree.rootref();
2903
2904 // serialize an integer, and mutate the tree
2905 CHECK(root[2].val() == "c");
2906 CHECK(root[2].val().is_sub(yml)); // val is first pointing at the buffer
2907 root[2].set_serialized(12345);
2908 CHECK(root[2].val() == "12345");
2909 CHECK(root[2].val().is_sub(tree.arena())); // now val is pointing at the arena
2910 // notice the serialized string was appended to the tree's arena:
2911 CHECK(tree.arena() == "12345");
2912
2913 // serialize an integer, and mutate the tree
2914 CHECK(root[3].val() == "d");
2915 CHECK(root[3].val().is_sub(yml)); // val is first pointing at the buffer
2916 root[3].set_serialized(67890);
2917 CHECK(root[3].val() == "67890");
2918 CHECK(root[3].val().is_sub(tree.arena())); // now val is pointing at the arena
2919 // notice the serialized string was appended to the tree's arena:
2920 CHECK(tree.arena() == "1234567890");
2921 }
2922 // the arena is also used when the data is serialized to string
2923 // second example: parse in arena
2924 {
2925 ryml::csubstr yml = "[a, b, c, d]"; // immutable
2927 // notice the buffer was copied to the arena:
2928 CHECK(tree.arena().data() != yml.data());
2929 CHECK(tree.arena() == yml);
2930 ryml::NodeRef root = tree.rootref();
2931
2932 // serialize an integer, and mutate the tree
2933 CHECK(root[2].val() == "c");
2934 root[2].set_serialized(12345); // serialize an integer
2935 CHECK(root[2].val() == "12345");
2936 // notice the serialized string was appended to the tree's arena:
2937 // notice also the previous values remain there.
2938 // RYML DOES NOT KEEP TRACK OF REFERENCES TO THE ARENA.
2939 CHECK(tree.arena() == "[a, b, c, d]12345");
2940 // old values: --------------^
2941
2942 // serialize an integer, and mutate the tree
2943 root[3].set_serialized(67890);
2944 CHECK(root[3].val() == "67890");
2945 // notice the serialized string was appended to the tree's arena:
2946 // notice also the previous values remain there.
2947 // RYML DOES NOT KEEP TRACK OF REFERENCES TO THE ARENA.
2948 CHECK(tree.arena() == "[a, b, c, d]1234567890");
2949 // old values: --------------^ ---^^^^^
2950 }
2951
2952 // to_arena(): directly serialize values to the arena:
2953 {
2954 ryml::Tree tree = ryml::parse_in_arena("{a: b}");
2955 ryml::csubstr c10 = tree.to_arena(10101010);
2956 CHECK(c10 == "10101010");
2957 CHECK(c10.is_sub(tree.arena()));
2958 CHECK(tree.arena() == "{a: b}10101010");
2959 CHECK(tree.key(1) == "a");
2960 CHECK(tree.val(1) == "b");
2961 tree.set_val(1, c10);
2962 CHECK(tree.val(1) == c10);
2963 // and you can also do it through a node:
2964 ryml::NodeRef root = tree.rootref();
2965 root["a"].set_serialized(2222);
2966 CHECK(root["a"].val() == "2222");
2967 CHECK(tree.arena() == "{a: b}101010102222");
2968 }
2969
2970 // copy_to_arena(): manually copy a string to the arena:
2971 {
2972 ryml::Tree tree = ryml::parse_in_arena("{a: b}");
2973 ryml::csubstr mystr = "Gosset Grande Reserve";
2974 ryml::csubstr copied = tree.copy_to_arena(mystr);
2975 CHECK(!copied.overlaps(mystr));
2976 CHECK(copied == mystr);
2977 CHECK(tree.arena() == "{a: b}Gosset Grande Reserve");
2978 }
2979
2980 // alloc_arena(): allocate a buffer from the arena:
2981 {
2982 ryml::Tree tree = ryml::parse_in_arena("{a: b}");
2983 ryml::csubstr mystr = "Gosset Grande Reserve";
2984 ryml::substr copied = tree.alloc_arena(mystr.size());
2985 CHECK(!copied.overlaps(mystr));
2986 memcpy(copied.str, mystr.str, mystr.len);
2987 CHECK(copied == mystr);
2988 CHECK(tree.arena() == "{a: b}Gosset Grande Reserve");
2989 }
2990
2991 // reserve_arena(): ensure the arena has a certain size to avoid reallocations
2992 {
2993 ryml::Tree tree = ryml::parse_in_arena("{a: b}");
2994 CHECK(tree.arena().size() == strlen("{a: b}"));
2995 tree.reserve_arena(100);
2996 CHECK(tree.arena_capacity() >= 100);
2997 CHECK(tree.arena().size() == strlen("{a: b}"));
2998 tree.to_arena(123456);
2999 CHECK(tree.arena().first(12) == "{a: b}123456");
3000 }
3001}
3002
3003
3004//-----------------------------------------------------------------------------
3005
3006/** ryml provides facilities for serializing and deserializing the C++
3007 fundamental types, including boolean and null values; this is
3008 provided by the several overloads in @ref doc_to_chars and @ref
3009 doc_from_chars. To add serialization for user scalar types (ie,
3010 those types that should be serialized as strings in leaf nodes),
3011 you just need to define the appropriate overloads of to_chars and
3012 from_chars for those types; see @ref sample_user_scalar_types for
3013 an example on how to achieve this, and see @ref doc_serialization
3014 for more information on serialization. */
3016{
3017 ryml::Tree tree;
3018 CHECK(tree.arena().empty());
3019 CHECK(tree.to_arena('a') == "a"); CHECK(tree.arena() == "a");
3020 CHECK(tree.to_arena("bcde") == "bcde"); CHECK(tree.arena() == "abcde");
3021 CHECK(tree.to_arena(unsigned(0)) == "0"); CHECK(tree.arena() == "abcde0");
3022 CHECK(tree.to_arena(int(1)) == "1"); CHECK(tree.arena() == "abcde01");
3023 CHECK(tree.to_arena(uint8_t(0)) == "0"); CHECK(tree.arena() == "abcde010");
3024 CHECK(tree.to_arena(uint16_t(1)) == "1"); CHECK(tree.arena() == "abcde0101");
3025 CHECK(tree.to_arena(uint32_t(2)) == "2"); CHECK(tree.arena() == "abcde01012");
3026 CHECK(tree.to_arena(uint64_t(3)) == "3"); CHECK(tree.arena() == "abcde010123");
3027 CHECK(tree.to_arena(int8_t( 4)) == "4"); CHECK(tree.arena() == "abcde0101234");
3028 CHECK(tree.to_arena(int8_t(-4)) == "-4"); CHECK(tree.arena() == "abcde0101234-4");
3029 CHECK(tree.to_arena(int16_t( 5)) == "5"); CHECK(tree.arena() == "abcde0101234-45");
3030 CHECK(tree.to_arena(int16_t(-5)) == "-5"); CHECK(tree.arena() == "abcde0101234-45-5");
3031 CHECK(tree.to_arena(int32_t( 6)) == "6"); CHECK(tree.arena() == "abcde0101234-45-56");
3032 CHECK(tree.to_arena(int32_t(-6)) == "-6"); CHECK(tree.arena() == "abcde0101234-45-56-6");
3033 CHECK(tree.to_arena(int64_t( 7)) == "7"); CHECK(tree.arena() == "abcde0101234-45-56-67");
3034 CHECK(tree.to_arena(int64_t(-7)) == "-7"); CHECK(tree.arena() == "abcde0101234-45-56-67-7");
3035 CHECK(tree.to_arena((void*)1) == "0x1"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x1");
3036 CHECK(tree.to_arena(float(0.124)) == "0.124"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.124");
3037 CHECK(tree.to_arena(double(0.234)) == "0.234"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.234");
3038
3039 // write boolean values - see also sample_formatting()
3040 CHECK(tree.to_arena(bool(true)) == "1"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.2341");
3041 CHECK(tree.to_arena(bool(false)) == "0"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410");
3042 CHECK(tree.to_arena(c4::fmt::boolalpha(true)) == "true"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410true");
3043 CHECK(tree.to_arena(c4::fmt::boolalpha(false)) == "false"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410truefalse");
3044
3045 // write special float values
3046 // see also sample_float_precision()
3047 const float fnan = std::numeric_limits<float >::quiet_NaN();
3048 const double dnan = std::numeric_limits<double>::quiet_NaN();
3049 const float finf = std::numeric_limits<float >::infinity();
3050 const double dinf = std::numeric_limits<double>::infinity();
3051 CHECK(tree.to_arena( finf) == ".inf"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410truefalse.inf");
3052 CHECK(tree.to_arena( dinf) == ".inf"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410truefalse.inf.inf");
3053 CHECK(tree.to_arena(-finf) == "-.inf"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410truefalse.inf.inf-.inf");
3054 CHECK(tree.to_arena(-dinf) == "-.inf"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410truefalse.inf.inf-.inf-.inf");
3055 CHECK(tree.to_arena( fnan) == ".nan"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410truefalse.inf.inf-.inf-.inf.nan");
3056 CHECK(tree.to_arena( dnan) == ".nan"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410truefalse.inf.inf-.inf-.inf.nan.nan");
3057
3058 // read special float values
3059 // see also sample_float_precision()
3060 C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH("-Wfloat-equal");
3061 tree = ryml::parse_in_arena(R"({ninf: -.inf, pinf: .inf, nan: .nan})");
3062 float f = 0.f;
3063 double d = 0.;
3064 CHECK(f == 0.f);
3065 CHECK(d == 0.);
3066 tree["ninf"].load(&f); CHECK(f == -finf);
3067 tree["ninf"].load(&d); CHECK(d == -dinf);
3068 tree["pinf"].load(&f); CHECK(f == finf);
3069 tree["pinf"].load(&d); CHECK(d == dinf);
3070 tree["nan" ].load(&f); CHECK(std::isnan(f));
3071 tree["nan" ].load(&d); CHECK(std::isnan(d));
3072 C4_SUPPRESS_WARNING_GCC_CLANG_POP
3073
3074 // value overflow detection:
3075 // (for integral types only)
3076 {
3077 // we will be detecting errors below, so we use this sample helper
3079 ryml::Tree t(err.callbacks()); // instantiate with the error-detecting callbacks
3080 // create a simple tree with an int value
3081 ryml::parse_in_arena(R"({val: 258})", &t);
3082 // by default, overflow is not detected:
3083 uint8_t valu8 = 0;
3084 int8_t vali8 = 0;
3085 ryml::ConstNodeRef n = t["val"];
3086 n.load(&valu8); CHECK(valu8 == 2); // not 257; it wrapped around
3087 n.load(&vali8); CHECK(vali8 == 2); // not 257; it wrapped around
3088 // ...but there are facilities to detect overflow
3092 // and there is a format helper
3093 CHECK(err.check_error_occurs([&]{
3094 n.load(ryml::fmt::overflow_checked(valu8)); // this will cause an error
3095 }));
3096 CHECK(err.check_error_occurs([&]{
3097 n.load(ryml::fmt::overflow_checked(vali8)); // this will cause an error
3098 }));
3099 }
3100}
3101
3102
3103//-----------------------------------------------------------------------------
3104
3105/** Shows how to deal with empty/null values. See also @ref
3106 * c4::yml::Tree::val_is_null */
3108{
3109 // reading empty/null values - see also sample_formatting()
3111 "plain:" "\n"
3112 "squoted: ''" "\n"
3113 "dquoted: \"\"" "\n"
3114 "literal: |" "\n"
3115 "folded: >" "\n"
3116 "all_null: [~, null, Null, NULL]" "\n"
3117 "non_null: [nULL, non_null, non null, null it is not]" "\n"
3118 "");
3119 // first, remember that .has_val() is a structural predicate
3120 // indicating the node is a leaf, and not a container.
3121 CHECK(tree["plain"].has_val()); // has a val, even if it's empty!
3122 CHECK(tree["squoted"].has_val());
3123 CHECK(tree["dquoted"].has_val());
3124 CHECK(tree["literal"].has_val());
3125 CHECK(tree["folded"].has_val());
3126 CHECK( ! tree["all_null"].has_val());
3127 CHECK( ! tree["non_null"].has_val());
3128 // In essence, has_val() is the logical opposite of is_container()
3129 CHECK( ! tree["plain"].is_container());
3130 CHECK( ! tree["squoted"].is_container());
3131 CHECK( ! tree["dquoted"].is_container());
3132 CHECK( ! tree["literal"].is_container());
3133 CHECK( ! tree["folded"].is_container());
3134 CHECK(tree["all_null"].is_container());
3135 CHECK(tree["non_null"].is_container());
3136 //
3137 // Right. How about the contents of each val?
3138 //
3139 // all of these scalars have zero-length:
3140 CHECK(tree["plain"].val().len == 0);
3141 CHECK(tree["squoted"].val().len == 0);
3142 CHECK(tree["dquoted"].val().len == 0);
3143 CHECK(tree["literal"].val().len == 0);
3144 CHECK(tree["folded"].val().len == 0);
3145 // but only the empty scalar has null string:
3146 CHECK(tree["plain"].val().str == nullptr);
3147 CHECK(tree["squoted"].val().str != nullptr);
3148 CHECK(tree["dquoted"].val().str != nullptr);
3149 CHECK(tree["literal"].val().str != nullptr);
3150 CHECK(tree["folded"].val().str != nullptr);
3151 // likewise, scalar comparison to nullptr has the same results:
3152 // (remember that .val() gives you the scalar value, node must
3153 // have a val, ie must be a leaf node, not a container)
3154 CHECK(tree["plain"].val() == nullptr);
3155 CHECK(tree["squoted"].val() != nullptr);
3156 CHECK(tree["dquoted"].val() != nullptr);
3157 CHECK(tree["literal"].val() != nullptr);
3158 CHECK(tree["folded"].val() != nullptr);
3159 // the tree and node classes provide the corresponding predicate
3160 // functions .key_is_null() and .val_is_null().
3161 // (note that these functions have the same preconditions as .val(),
3162 // because they need get the val to look into its contents)
3163 CHECK(tree["plain"].val_is_null());
3164 CHECK( ! tree["squoted"].val_is_null());
3165 CHECK( ! tree["dquoted"].val_is_null());
3166 CHECK( ! tree["literal"].val_is_null());
3167 CHECK( ! tree["folded"].val_is_null());
3168 // matching to null is case-sensitive. only the cases shown here
3169 // match to null:
3170 for(ryml::ConstNodeRef child : tree["all_null"].children())
3171 {
3172 CHECK(child.val() != nullptr); // it is pointing at a string, so it is not nullptr!
3173 CHECK(child.val_is_null());
3174 }
3175 for(ryml::ConstNodeRef child : tree["non_null"].children())
3176 {
3177 CHECK(child.val() != nullptr);
3178 CHECK( ! child.val_is_null());
3179 }
3180 //
3181 //
3182 // Because the meaning of null/~/empty will vary from application
3183 // to application, ryml makes no assumption on what should be
3184 // serialized as null. It leaves this decision to the user. But
3185 // it also provides the proper toolbox for the user to implement
3186 // its intended solution.
3187 //
3188 // writing/disambiguating null values:
3189 ryml::csubstr null = {};
3190 ryml::csubstr nonnull = "";
3191 ryml::csubstr strnull = "null";
3192 ryml::csubstr tilde = "~";
3193 CHECK(null .len == 0); CHECK(null .str == nullptr); CHECK(null == nullptr);
3194 CHECK(nonnull.len == 0); CHECK(nonnull.str != nullptr); CHECK(nonnull != nullptr);
3195 CHECK(strnull.len != 0); CHECK(strnull.str != nullptr); CHECK(strnull != nullptr);
3196 CHECK(tilde .len != 0); CHECK(tilde .str != nullptr); CHECK(tilde != nullptr);
3197 tree.clear();
3198 tree.clear_arena();
3199 tree.rootref().set_map();
3200 // serializes as an empty plain scalar:
3201 tree["empty_null"].set_serialized(null); CHECK(tree.arena() == "");
3202 // serializes as an empty quoted scalar:
3203 tree["empty_nonnull"].set_serialized(nonnull); CHECK(tree.arena() == "");
3204 // serializes as the normal 'null' string:
3205 tree["str_null"].set_serialized(strnull); CHECK(tree.arena() == "null");
3206 // serializes as the normal '~' string:
3207 tree["str_tilde"].set_serialized(tilde); CHECK(tree.arena() == "null~");
3208 // this is the resulting yaml:
3210 "empty_null: " "\n"
3211 "empty_nonnull: ''" "\n"
3212 "str_null: null" "\n"
3213 "str_tilde: ~" "\n"
3214 "");
3215 // To enforce a particular concept of what is a null string, you
3216 // can use the appropriate condition based on pointer nullity or
3217 // other appropriate criteria.
3218 //
3219 // As an example, proper comparison to nullptr:
3220 auto null_if_nullptr = [](ryml::csubstr s) {
3221 return s.str == nullptr ? "null" : s;
3222 };
3223 tree["empty_null"].set_serialized(null_if_nullptr(null));
3224 tree["empty_nonnull"].set_serialized(null_if_nullptr(nonnull));
3225 tree["str_null"].set_serialized(null_if_nullptr(strnull));
3226 tree["str_tilde"].set_serialized(null_if_nullptr(tilde));
3227 // this is the resulting yaml:
3229 "empty_null: null" "\n"
3230 "empty_nonnull: ''" "\n"
3231 "str_null: null" "\n"
3232 "str_tilde: ~" "\n"
3233 "");
3234 //
3235 // As another example, nullity check based on the YAML nullity
3236 // predicate:
3237 auto null_if_predicate = [](ryml::csubstr s) {
3238 return ryml::scalar_is_null(s) ? "null" : s;
3239 };
3240 tree["empty_null"].set_serialized(null_if_predicate(null));
3241 tree["empty_nonnull"].set_serialized(null_if_predicate(nonnull));
3242 tree["str_null"].set_serialized(null_if_predicate(strnull));
3243 tree["str_tilde"].set_serialized(null_if_predicate(tilde));
3244 // this is the resulting yaml:
3246 "empty_null: null" "\n"
3247 "empty_nonnull: ''" "\n"
3248 "str_null: null" "\n"
3249 "str_tilde: null" "\n"
3250 "");
3251 //
3252 // As another example, nullity check based on the YAML nullity
3253 // predicate, but returning "~" to simbolize nullity:
3254 auto tilde_if_predicate = [](ryml::csubstr s) {
3255 return ryml::scalar_is_null(s) ? "~" : s;
3256 };
3257 tree["empty_null"].set_serialized(tilde_if_predicate(null));
3258 tree["empty_nonnull"].set_serialized(tilde_if_predicate(nonnull));
3259 tree["str_null"].set_serialized(tilde_if_predicate(strnull));
3260 tree["str_tilde"].set_serialized(tilde_if_predicate(tilde));
3261 // this is the resulting yaml:
3263 "empty_null: ~" "\n"
3264 "empty_nonnull: ''" "\n"
3265 "str_null: ~" "\n"
3266 "str_tilde: ~" "\n"
3267 "");
3268}
3269
3270
3271//-----------------------------------------------------------------------------
3272
3273/** ryml provides facilities for formatting/deformatting (imported
3274 * from c4core into the ryml namespace). See @ref doc_format_utils
3275 * . These functions are very useful to serialize and deserialize
3276 * scalar types; see @ref doc_serialization .
3277 */
3279{
3280 // format(), format_sub(), formatrs(): format arguments
3281 {
3282 char buf_[256] = {};
3283 ryml::substr buf = buf_;
3284 size_t size = ryml::format(buf, "a={} foo {} {} bar {}", 0.1, 10, 11, 12);
3285 CHECK(size == strlen("a=0.1 foo 10 11 bar 12"));
3286 CHECK(buf.first(size) == "a=0.1 foo 10 11 bar 12");
3287 // it is safe to call on an empty buffer:
3288 // returns the size needed for the result, and no overflow occurs:
3289 size = ryml::format({} , "a={} foo {} {} bar {}", "this_is_a", 10, 11, 12);
3290 CHECK(size == ryml::format(buf, "a={} foo {} {} bar {}", "this_is_a", 10, 11, 12));
3291 CHECK(size == strlen("a=this_is_a foo 10 11 bar 12"));
3292 // it is also safe to call on an insufficient buffer:
3293 char smallbuf[8] = {};
3294 size = ryml::format(smallbuf, "{} is too large {}", "this", "for the buffer");
3295 CHECK(size == strlen("this is too large for the buffer"));
3296 // ... and the result is truncated at the buffer size:
3297 CHECK(ryml::substr(smallbuf, sizeof(smallbuf)) == "this is\0");
3298
3299 // format_sub() directly returns the written string:
3300 ryml::csubstr result = ryml::format_sub(buf, "b={}, damn it.", 1);
3301 CHECK(result == "b=1, damn it.");
3302 CHECK(result.is_sub(buf));
3303
3304 // formatrs() means FORMAT & ReSize:
3305 //
3306 // Instead of a substr, it receives any owning linear char container
3307 // for which to_substr() is defined (using ADL).
3308 // <ryml_std.hpp> has to_substr() definitions for std::string and
3309 // std::vector<char>.
3310 //
3311 // formatrs() starts by calling format(), and if needed, resizes the container
3312 // and calls format() again.
3313 //
3314 // Note that unless the container is previously sized, this
3315 // may cause an allocation, which will make your code slower.
3316 // Make sure to call .reserve() on the container for real
3317 // production code.
3318 std::string sbuf;
3319 ryml::formatrs(&sbuf, "and c={} seems about right", 2);
3320 CHECK(sbuf == "and c=2 seems about right");
3321 std::vector<char> vbuf; // works with any linear char container
3322 ryml::formatrs(&vbuf, "and c={} seems about right", 2);
3323 CHECK(sbuf == "and c=2 seems about right");
3324 // with formatrs() it is also possible to append:
3325 ryml::formatrs_append(&sbuf, ", and finally d={} - done", 3);
3326 CHECK(sbuf == "and c=2 seems about right, and finally d=3 - done");
3327 }
3328
3329 // unformat(): read arguments - opposite of format()
3330 {
3331 char buf_[256];
3332
3333 int a = 0, b = 1, c = 2;
3334 ryml::csubstr result = ryml::format_sub(buf_, "{} and {} and {}", a, b, c);
3335 CHECK(result == "0 and 1 and 2");
3336 int aa = -1, bb = -2, cc = -3;
3337 size_t num_characters = ryml::unformat(result, "{} and {} and {}", aa, bb, cc);
3338 CHECK(num_characters != ryml::csubstr::npos); // if a conversion fails, returns ryml::csubstr::npos
3339 CHECK(num_characters == result.size());
3340 CHECK(aa == a);
3341 CHECK(bb == b);
3342 CHECK(cc == c);
3343
3344 result = ryml::format_sub(buf_, "{} and {} and {}", 10, 20, 30);
3345 CHECK(result == "10 and 20 and 30");
3346 num_characters = ryml::unformat(result, "{} and {} and {}", aa, bb, cc);
3347 CHECK(num_characters != ryml::csubstr::npos); // if a conversion fails, returns ryml::csubstr::npos
3348 CHECK(num_characters == result.size());
3349 CHECK(aa == 10);
3350 CHECK(bb == 20);
3351 CHECK(cc == 30);
3352 }
3353
3354 // cat(), cat_sub(), catrs(): concatenate arguments
3355 {
3356 char buf_[256] = {};
3357 ryml::substr buf = buf_;
3358 size_t size = ryml::cat(buf, "a=", 0.1, "foo", 10, 11, "bar", 12);
3359 CHECK(size == strlen("a=0.1foo1011bar12"));
3360 CHECK(buf.first(size) == "a=0.1foo1011bar12");
3361 // it is safe to call on an empty buffer:
3362 // returns the size needed for the result, and no overflow occurs:
3363 CHECK(ryml::cat({}, "a=", 0) == 3);
3364 // it is also safe to call on an insufficient buffer:
3365 char smallbuf[8] = {};
3366 size = ryml::cat(smallbuf, "this", " is too large ", "for the buffer");
3367 CHECK(size == strlen("this is too large for the buffer"));
3368 // ... and the result is truncated at the buffer size:
3369 CHECK(ryml::substr(smallbuf, sizeof(smallbuf)) == "this is\0");
3370
3371 // cat_sub() directly returns the written string:
3372 ryml::csubstr result = ryml::cat_sub(buf, "b=", 1, ", damn it.");
3373 CHECK(result == "b=1, damn it.");
3374 CHECK(result.is_sub(buf));
3375
3376 // catrs() means CAT & ReSize:
3377 //
3378 // Instead of a substr, it receives any owning linear char container
3379 // for which to_substr() is defined (using ADL).
3380 // <ryml_std.hpp> has to_substr() definitions for std::string and
3381 // std::vector<char>.
3382 //
3383 // catrs() starts by calling cat(), and if needed, resizes the container
3384 // and calls cat() again.
3385 //
3386 // Note that unless the container is previously sized, this
3387 // may cause an allocation, which will make your code slower.
3388 // Make sure to call .reserve() on the container for real
3389 // production code.
3390 std::string sbuf;
3391 ryml::catrs(&sbuf, "and c=", 2, " seems about right");
3392 CHECK(sbuf == "and c=2 seems about right");
3393 std::vector<char> vbuf; // works with any linear char container
3394 ryml::catrs(&vbuf, "and c=", 2, " seems about right");
3395 CHECK(sbuf == "and c=2 seems about right");
3396 // with catrs() it is also possible to append:
3397 ryml::catrs_append(&sbuf, ", and finally d=", 3, " - done");
3398 CHECK(sbuf == "and c=2 seems about right, and finally d=3 - done");
3399 }
3400
3401 // uncat(): read arguments - opposite of cat()
3402 {
3403 char buf_[256];
3404
3405 int a = 0, b = 1, c = 2;
3406 ryml::csubstr result = ryml::cat_sub(buf_, a, ' ', b, ' ', c);
3407 CHECK(result == "0 1 2");
3408 int aa = -1, bb = -2, cc = -3;
3409 char sep1 = 'a', sep2 = 'b';
3410 size_t num_characters = ryml::uncat(result, aa, sep1, bb, sep2, cc);
3411 CHECK(num_characters == result.size());
3412 CHECK(aa == a);
3413 CHECK(bb == b);
3414 CHECK(cc == c);
3415 CHECK(sep1 == ' ');
3416 CHECK(sep2 == ' ');
3417
3418 result = ryml::cat_sub(buf_, 10, ' ', 20, ' ', 30);
3419 CHECK(result == "10 20 30");
3420 num_characters = ryml::uncat(result, aa, sep1, bb, sep2, cc);
3421 CHECK(num_characters == result.size());
3422 CHECK(aa == 10);
3423 CHECK(bb == 20);
3424 CHECK(cc == 30);
3425 CHECK(sep1 == ' ');
3426 CHECK(sep2 == ' ');
3427 }
3428
3429 // catsep(), catsep_sub(), catseprs(): concatenate arguments, with a separator
3430 {
3431 char buf_[256] = {};
3432 ryml::substr buf = buf_;
3433 // use ' ' as a separator
3434 size_t size = ryml::catsep(buf, ' ', "a=", 0, "b=", 1, "c=", 2, 45, 67);
3435 CHECK(buf.first(size) == "a= 0 b= 1 c= 2 45 67");
3436 // any separator may be used
3437 // use " and " as a separator
3438 size = ryml::catsep(buf, " and ", "a=0", "b=1", "c=2", 45, 67);
3439 CHECK(buf.first(size) == "a=0 and b=1 and c=2 and 45 and 67");
3440 // use " ... " as a separator
3441 size = ryml::catsep(buf, " ... ", "a=0", "b=1", "c=2", 45, 67);
3442 CHECK(buf.first(size) == "a=0 ... b=1 ... c=2 ... 45 ... 67");
3443 // use '/' as a separator
3444 size = ryml::catsep(buf, '/', "a=", 0, "b=", 1, "c=", 2, 45, 67);
3445 CHECK(buf.first(size) == "a=/0/b=/1/c=/2/45/67");
3446 // use 888 as a separator
3447 size = ryml::catsep(buf, 888, "a=0", "b=1", "c=2", 45, 67);
3448 CHECK(buf.first(size) == "a=0888b=1888c=28884588867");
3449
3450 // it is safe to call on an empty buffer:
3451 // returns the size needed for the result, and no overflow occurs:
3452 CHECK(size == ryml::catsep({}, 888, "a=0", "b=1", "c=2", 45, 67));
3453 // it is also safe to call on an insufficient buffer:
3454 char smallbuf[8] = {};
3455 CHECK(size == ryml::catsep(smallbuf, 888, "a=0", "b=1", "c=2", 45, 67));
3456 CHECK(size == strlen("a=0888b=1888c=28884588867"));
3457 // ... and the result is truncated:
3458 CHECK(ryml::substr(smallbuf, sizeof(smallbuf)) == "a=0888b\0");
3459
3460 // catsep_sub() directly returns the written substr:
3461 ryml::csubstr result = ryml::catsep_sub(buf, " and ", "a=0", "b=1", "c=2", 45, 67);
3462 CHECK(result == "a=0 and b=1 and c=2 and 45 and 67");
3463 CHECK(result.is_sub(buf));
3464
3465 // catseprs() means CATSEP & ReSize:
3466 //
3467 // Instead of a substr, it receives any owning linear char container
3468 // for which to_substr() is defined (using ADL).
3469 // <ryml_std.hpp> has to_substr() definitions for std::string and
3470 // std::vector<char>.
3471 //
3472 // catseprs() starts by calling catsep(), and if needed, resizes the container
3473 // and calls catsep() again.
3474 //
3475 // Note that unless the container is previously sized, this
3476 // may cause an allocation, which will make your code slower.
3477 // Make sure to call .reserve() on the container for real
3478 // production code.
3479 std::string sbuf;
3480 ryml::catseprs(&sbuf, " and ", "a=0", "b=1", "c=2", 45, 67);
3481 CHECK(sbuf == "a=0 and b=1 and c=2 and 45 and 67");
3482 std::vector<char> vbuf; // works with any linear char container
3483 ryml::catseprs(&vbuf, " and ", "a=0", "b=1", "c=2", 45, 67);
3484 CHECK(ryml::to_csubstr(vbuf) == "a=0 and b=1 and c=2 and 45 and 67");
3485
3486 // with catseprs() it is also possible to append:
3487 ryml::catseprs_append(&sbuf, " well ", " --- a=0", "b=11", "c=12", 145, 167);
3488 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");
3489 }
3490
3491 // uncatsep(): read arguments with a separator - opposite of catsep()
3492 {
3493 char buf_[256] = {};
3494
3495 int a = 0, b = 1, c = 2;
3496 ryml::csubstr result = ryml::catsep_sub(buf_, ' ', a, b, c);
3497 CHECK(result == "0 1 2");
3498 int aa = -1, bb = -2, cc = -3;
3499 size_t num_characters = ryml::uncatsep(result, " ", aa, bb, cc);
3500 CHECK(num_characters == result.size());
3501 CHECK(aa == a);
3502 CHECK(bb == b);
3503 CHECK(cc == c);
3504
3505 result = ryml::catsep_sub(buf_, "--", 10, 20, 30);
3506 CHECK(result == "10--20--30");
3507 num_characters = ryml::uncatsep(result, "--", aa, bb, cc);
3508 CHECK(num_characters == result.size());
3509 CHECK(aa == 10);
3510 CHECK(bb == 20);
3511 CHECK(cc == 30);
3512 }
3513
3514 // formatting individual arguments
3515 {
3516 using namespace ryml; // all the symbols below are in the ryml namespace.
3517 char buf_[256] = {}; // all the results below are written in this buffer
3518 substr buf = buf_;
3519 // --------------------------------------
3520 // fmt::boolalpha(): format as true/false
3521 // --------------------------------------
3522 // just as with std streams, printing a bool will output the integer value:
3523 CHECK("0" == cat_sub(buf, false));
3524 CHECK("1" == cat_sub(buf, true));
3525 // to force a "true"/"false", use fmt::boolalpha:
3526 CHECK("false" == cat_sub(buf, fmt::boolalpha(false)));
3527 CHECK("true" == cat_sub(buf, fmt::boolalpha(true)));
3528
3529 // ---------------------------------
3530 // fmt::hex(): format as hexadecimal
3531 // ---------------------------------
3532 CHECK("0xff" == cat_sub(buf, fmt::hex(255)));
3533 CHECK("0x100" == cat_sub(buf, fmt::hex(256)));
3534 CHECK("-0xff" == cat_sub(buf, fmt::hex(-255)));
3535 CHECK("-0x100" == cat_sub(buf, fmt::hex(-256)));
3536 CHECK("3735928559" == cat_sub(buf, UINT32_C(0xdeadbeef)));
3537 CHECK("0xdeadbeef" == cat_sub(buf, fmt::hex(UINT32_C(0xdeadbeef))));
3538 // ----------------------------
3539 // fmt::bin(): format as binary
3540 // ----------------------------
3541 CHECK("0b1000" == cat_sub(buf, fmt::bin(8)));
3542 CHECK("0b1001" == cat_sub(buf, fmt::bin(9)));
3543 CHECK("0b10001" == cat_sub(buf, fmt::bin(17)));
3544 CHECK("0b11001" == cat_sub(buf, fmt::bin(25)));
3545 CHECK("-0b1000" == cat_sub(buf, fmt::bin(-8)));
3546 CHECK("-0b1001" == cat_sub(buf, fmt::bin(-9)));
3547 CHECK("-0b10001" == cat_sub(buf, fmt::bin(-17)));
3548 CHECK("-0b11001" == cat_sub(buf, fmt::bin(-25)));
3549 // ---------------------------
3550 // fmt::bin(): format as octal
3551 // ---------------------------
3552 CHECK("0o77" == cat_sub(buf, fmt::oct(63)));
3553 CHECK("0o100" == cat_sub(buf, fmt::oct(64)));
3554 CHECK("0o377" == cat_sub(buf, fmt::oct(255)));
3555 CHECK("0o400" == cat_sub(buf, fmt::oct(256)));
3556 CHECK("0o1000" == cat_sub(buf, fmt::oct(512)));
3557 CHECK("-0o77" == cat_sub(buf, fmt::oct(-63)));
3558 CHECK("-0o100" == cat_sub(buf, fmt::oct(-64)));
3559 CHECK("-0o377" == cat_sub(buf, fmt::oct(-255)));
3560 CHECK("-0o400" == cat_sub(buf, fmt::oct(-256)));
3561 CHECK("-0o1000" == cat_sub(buf, fmt::oct(-512)));
3562 // ---------------------------
3563 // fmt::zpad(): pad with zeros
3564 // ---------------------------
3565 CHECK("000063" == cat_sub(buf, fmt::zpad(63, 6)));
3566 CHECK( "00063" == cat_sub(buf, fmt::zpad(63, 5)));
3567 CHECK( "0063" == cat_sub(buf, fmt::zpad(63, 4)));
3568 CHECK( "063" == cat_sub(buf, fmt::zpad(63, 3)));
3569 CHECK( "63" == cat_sub(buf, fmt::zpad(63, 2)));
3570 CHECK( "63" == cat_sub(buf, fmt::zpad(63, 1))); // will never trim the result
3571 CHECK( "63" == cat_sub(buf, fmt::zpad(63, 0))); // will never trim the result
3572 CHECK("0x00003f" == cat_sub(buf, fmt::zpad(fmt::hex(63), 6)));
3573 CHECK("0o000077" == cat_sub(buf, fmt::zpad(fmt::oct(63), 6)));
3574 CHECK("0b00011001" == cat_sub(buf, fmt::zpad(fmt::bin(25), 8)));
3575 // ------------------------------------------------
3576 // fmt::left(): align left with a given field width
3577 // ------------------------------------------------
3578 CHECK("63 " == cat_sub(buf, fmt::left(63, 6)));
3579 CHECK("63 " == cat_sub(buf, fmt::left(63, 5)));
3580 CHECK("63 " == cat_sub(buf, fmt::left(63, 4)));
3581 CHECK("63 " == cat_sub(buf, fmt::left(63, 3)));
3582 CHECK("63" == cat_sub(buf, fmt::left(63, 2)));
3583 CHECK("63" == cat_sub(buf, fmt::left(63, 1))); // will never trim the result
3584 CHECK("63" == cat_sub(buf, fmt::left(63, 0))); // will never trim the result
3585 // the fill character can be specified (defaults to ' '):
3586 CHECK("63----" == cat_sub(buf, fmt::left(63, 6, '-')));
3587 CHECK("63++++" == cat_sub(buf, fmt::left(63, 6, '+')));
3588 CHECK("63////" == cat_sub(buf, fmt::left(63, 6, '/')));
3589 CHECK("630000" == cat_sub(buf, fmt::left(63, 6, '0')));
3590 CHECK("63@@@@" == cat_sub(buf, fmt::left(63, 6, '@')));
3591 CHECK("0x003f " == cat_sub(buf, fmt::left(fmt::zpad(fmt::hex(63), 4), 10)));
3592 // --------------------------------------------------
3593 // fmt::right(): align right with a given field width
3594 // --------------------------------------------------
3595 CHECK(" 63" == cat_sub(buf, fmt::right(63, 6)));
3596 CHECK(" 63" == cat_sub(buf, fmt::right(63, 5)));
3597 CHECK(" 63" == cat_sub(buf, fmt::right(63, 4)));
3598 CHECK(" 63" == cat_sub(buf, fmt::right(63, 3)));
3599 CHECK("63" == cat_sub(buf, fmt::right(63, 2)));
3600 CHECK("63" == cat_sub(buf, fmt::right(63, 1))); // will never trim the result
3601 CHECK("63" == cat_sub(buf, fmt::right(63, 0))); // will never trim the result
3602 // the fill character can be specified (defaults to ' '):
3603 CHECK("----63" == cat_sub(buf, fmt::right(63, 6, '-')));
3604 CHECK("++++63" == cat_sub(buf, fmt::right(63, 6, '+')));
3605 CHECK("////63" == cat_sub(buf, fmt::right(63, 6, '/')));
3606 CHECK("000063" == cat_sub(buf, fmt::right(63, 6, '0')));
3607 CHECK("@@@@63" == cat_sub(buf, fmt::right(63, 6, '@')));
3608 CHECK(" 0x003f" == cat_sub(buf, fmt::right(fmt::zpad(fmt::hex(63), 4), 10)));
3609
3610 // ------------------------------------------
3611 // fmt::real(): format floating point numbers
3612 // ------------------------------------------
3613 // see also sample_float_precision()
3614 CHECK("0" == cat_sub(buf, fmt::real(0.01f, 0)));
3615 CHECK("0.0" == cat_sub(buf, fmt::real(0.01f, 1)));
3616 CHECK("0.01" == cat_sub(buf, fmt::real(0.01f, 2)));
3617 CHECK("0.010" == cat_sub(buf, fmt::real(0.01f, 3)));
3618 CHECK("0.0100" == cat_sub(buf, fmt::real(0.01f, 4)));
3619 CHECK("0.01000" == cat_sub(buf, fmt::real(0.01f, 5)));
3620 CHECK("1" == cat_sub(buf, fmt::real(1.01f, 0)));
3621 CHECK("1.0" == cat_sub(buf, fmt::real(1.01f, 1)));
3622 CHECK("1.01" == cat_sub(buf, fmt::real(1.01f, 2)));
3623 CHECK("1.010" == cat_sub(buf, fmt::real(1.01f, 3)));
3624 CHECK("1.0100" == cat_sub(buf, fmt::real(1.01f, 4)));
3625 CHECK("1.01000" == cat_sub(buf, fmt::real(1.01f, 5)));
3626 CHECK("1" == cat_sub(buf, fmt::real(1.234234234, 0)));
3627 CHECK("1.2" == cat_sub(buf, fmt::real(1.234234234, 1)));
3628 CHECK("1.23" == cat_sub(buf, fmt::real(1.234234234, 2)));
3629 CHECK("1.234" == cat_sub(buf, fmt::real(1.234234234, 3)));
3630 CHECK("1.2342" == cat_sub(buf, fmt::real(1.234234234, 4)));
3631 CHECK("1.23423" == cat_sub(buf, fmt::real(1.234234234, 5)));
3632 CHECK("1000000.00000" == cat_sub(buf, fmt::real(1000000.000000000, 5)));
3633 CHECK("1234234.23423" == cat_sub(buf, fmt::real(1234234.234234234, 5)));
3634 // AKA %f
3635 CHECK("1000000.00000" == cat_sub(buf, fmt::real(1000000.000000000, 5, FTOA_FLOAT))); // AKA %f, same as above
3636 CHECK("1234234.23423" == cat_sub(buf, fmt::real(1234234.234234234, 5, FTOA_FLOAT))); // AKA %f
3637 CHECK("1234234.2342" == cat_sub(buf, fmt::real(1234234.234234234, 4, FTOA_FLOAT))); // AKA %f
3638 CHECK("1234234.234" == cat_sub(buf, fmt::real(1234234.234234234, 3, FTOA_FLOAT))); // AKA %f
3639 CHECK("1234234.23" == cat_sub(buf, fmt::real(1234234.234234234, 2, FTOA_FLOAT))); // AKA %f
3640 // AKA %e
3641 CHECK("1.00000e+06" == cat_sub(buf, fmt::real(1000000.000000000, 5, FTOA_SCIENT))); // AKA %e
3642 CHECK("1.23423e+06" == cat_sub(buf, fmt::real(1234234.234234234, 5, FTOA_SCIENT))); // AKA %e
3643 CHECK("1.2342e+06" == cat_sub(buf, fmt::real(1234234.234234234, 4, FTOA_SCIENT))); // AKA %e
3644 CHECK("1.234e+06" == cat_sub(buf, fmt::real(1234234.234234234, 3, FTOA_SCIENT))); // AKA %e
3645 CHECK("1.23e+06" == cat_sub(buf, fmt::real(1234234.234234234, 2, FTOA_SCIENT))); // AKA %e
3646 // AKA %g
3647 CHECK("1e+06" == cat_sub(buf, fmt::real(1000000.000000000, 5, FTOA_FLEX))); // AKA %g
3648 CHECK("1.2342e+06" == cat_sub(buf, fmt::real(1234234.234234234, 5, FTOA_FLEX))); // AKA %g
3649 CHECK("1.234e+06" == cat_sub(buf, fmt::real(1234234.234234234, 4, FTOA_FLEX))); // AKA %g
3650 CHECK("1.23e+06" == cat_sub(buf, fmt::real(1234234.234234234, 3, FTOA_FLEX))); // AKA %g
3651 CHECK("1.2e+06" == cat_sub(buf, fmt::real(1234234.234234234, 2, FTOA_FLEX))); // AKA %g
3652 // FTOA_HEXA: AKA %a (hexadecimal formatting of floats)
3653 CHECK("0x1.e8480p+19" == cat_sub(buf, fmt::real(1000000.000000000, 5, FTOA_HEXA))); // AKA %a
3654 CHECK("0x1.2d53ap+20" == cat_sub(buf, fmt::real(1234234.234234234, 5, FTOA_HEXA))); // AKA %a
3655
3656 // --------------------------------------------------------------
3657 // fmt::raw(): dump data in raw (binary) machine format (respecting alignment)
3658 // --------------------------------------------------------------
3659 {
3660 C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH("-Wcast-align") // we're casting the values directly, so alignment is strictly respected.
3661 const uint32_t payload[] = {10, 20, 30, 40, UINT32_C(0xdeadbeef)};
3662 // (package payload as a substr, for comparison only)
3663 csubstr expected = csubstr((const char *)payload, sizeof(payload));
3664 csubstr actual = cat_sub(buf, fmt::raw(payload));
3665 CHECK(!actual.overlaps(expected));
3666 CHECK(0 == memcmp(expected.str, actual.str, expected.len));
3667 // also possible with variables:
3668 for(const uint32_t value : payload)
3669 {
3670 // (package payload as a substr, for comparison only)
3671 expected = csubstr((const char *)&value, sizeof(value));
3672 actual = cat_sub(buf, fmt::raw(value));
3673 CHECK(actual.size() == sizeof(uint32_t));
3674 CHECK(!actual.overlaps(expected));
3675 CHECK(0 == memcmp(expected.str, actual.str, expected.len));
3676 // with non-const data, fmt::craw() may be needed for disambiguation:
3677 actual = cat_sub(buf, fmt::craw(value));
3678 CHECK(actual.size() == sizeof(uint32_t));
3679 CHECK(!actual.overlaps(expected));
3680 CHECK(0 == memcmp(expected.str, actual.str, expected.len));
3681 //
3682 // read back:
3683 uint32_t result = 0;
3684 auto reader = fmt::raw(result);
3685 CHECK(uncat(actual, reader));
3686 // and compare:
3687 // (vs2017/release/32bit does not reload result from cache, so force it)
3688 CHECK(result == value); // roundtrip completed successfully
3689 }
3690 C4_SUPPRESS_WARNING_GCC_CLANG_POP
3691 }
3692
3693 // -------------------------
3694 // fmt::base64(): see below!
3695 // -------------------------
3696 }
3697}
3698
3699
3700//-----------------------------------------------------------------------------
3701
3702/** demonstrates how to read and write base64-encoded blobs.
3703 * @see @ref doc_base64 */
3705{
3706 // let's start by creating a tree with base64 vals and keys
3707 ryml::Tree tree;
3708 tree.rootref().set_map();
3709 struct text_and_base64 { ryml::csubstr text, base64; };
3710 text_and_base64 cases[] = {
3711 {{"Hello, World!"}, {"SGVsbG8sIFdvcmxkIQ=="}},
3712 {{"Brevity is the soul of wit."}, {"QnJldml0eSBpcyB0aGUgc291bCBvZiB3aXQu"}},
3713 {{"All that glitters is not gold."}, {"QWxsIHRoYXQgZ2xpdHRlcnMgaXMgbm90IGdvbGQu"}},
3714 };
3715 // to encode base64 and write the result to val:
3716 for(text_and_base64 c : cases)
3717 tree[c.text].set_serialized(ryml::fmt::base64(c.text));
3718 // to encode base64 and write the result to key:
3719 for(text_and_base64 c : cases)
3720 {
3721 ryml::NodeRef ch = tree.rootref().append_child();
3722 ch.set_key_serialized(ryml::fmt::base64(c.text));
3723 ch.set_serialized(c.text);
3724 }
3725 // check the result:
3726 for(text_and_base64 c : cases)
3727 {
3728 CHECK(tree[c.text].val() == c.base64);
3729 CHECK(tree[c.base64].val() == c.text);
3730 }
3731 // and this is how the YAML now looks:
3733 "Hello, World!: SGVsbG8sIFdvcmxkIQ==" "\n"
3734 "Brevity is the soul of wit.: QnJldml0eSBpcyB0aGUgc291bCBvZiB3aXQu" "\n"
3735 "All that glitters is not gold.: QWxsIHRoYXQgZ2xpdHRlcnMgaXMgbm90IGdvbGQu" "\n"
3736 // note that the keys below are base64-encoded
3737 "SGVsbG8sIFdvcmxkIQ==: Hello, World!" "\n"
3738 "QnJldml0eSBpcyB0aGUgc291bCBvZiB3aXQu: Brevity is the soul of wit." "\n"
3739 "QWxsIHRoYXQgZ2xpdHRlcnMgaXMgbm90IGdvbGQu: All that glitters is not gold." "\n"
3740 "");
3741 char buf1_[128], buf2_[128];
3742 ryml::substr buf1 = buf1_; // this is where we will write the result (using load())
3743 ryml::substr buf2 = buf2_; // this is where we will write the result (using deserialize()/deserialize_key())
3744 // to decode base64 and write the result to buf:
3745 for(const text_and_base64 c : cases)
3746 {
3747 // this decodes base64 and write the decoded result into an
3748 // existing buffer (buf1):
3749 size_t len = 0; // the decoded length
3750 tree[c.text].load(ryml::fmt::base64(buf1, &len));
3751 // The base64() tag function is used to get the
3752 // deserialization using c4::decode_base64(). This will
3753 // respect the limits of the buffer, and fail with an error if
3754 // the buffer is too small (or if the base64 encoding is
3755 // wrong). The optional second parameter is set to the decoded
3756 // size, ie, the length of the decoded result, which is also
3757 // the size required for the buffer.
3758 CHECK(len <= buf1.len);
3759 CHECK(c.text.len == len);
3760 CHECK(buf1.first(len) == c.text);
3761 // likewise for keys:
3762 tree[c.base64].load_key(ryml::fmt::base64(buf2, &len));
3763 CHECK(len <= buf2.len);
3764 CHECK(buf2.first(len) == c.text);
3765 //
3766 // interop with std::string:
3767 std::string result;
3768 tree[c.text].load(ryml::fmt::base64(result));
3769 CHECK(result == c.text);
3770 // likewise for keys:
3771 tree[c.base64].load_key(ryml::fmt::base64(result));
3772 CHECK(result == c.text);
3773 //
3774 // Manual interop with std::string: using substr.
3775 // This shows how to manually resize the destination
3776 // buffer, and is similar to the implementation for containers.
3777 result.clear(); // this is not needed. We do it just to show that the first call can fail.
3778 len = 0;
3779 // try to read into the buffer, and get back the required size
3780 // (in len)
3781 auto payload = ryml::fmt::base64(ryml::to_substr(result), &len);
3782 bool ok = tree[c.text].deserialize(payload);
3783 if(len > result.size()) // the size was not enough; resize and call again
3784 {
3785 CHECK(!ok);
3786 result.resize(len);
3787 payload = ryml::fmt::base64(ryml::to_substr(result), &len); // reassign
3788 ok = tree[c.text].deserialize(payload);
3789 }
3790 CHECK(ok);
3791 result.resize(len); // trim to the length of the decoded buffer
3792 CHECK(result == c.text);
3793 // likewise for keys
3794 }
3795 //
3796 // ryml base64() serialization uses native endianness. If you're
3797 // encoding types whose size > 1 byte, the results will vary
3798 // according to endianess. Let's use a helper here to work around
3799 // that (in practice, you should use something like htons() before
3800 // encoding):
3801 union { uint32_t u; char c[sizeof(uint32_t)]; } endianess_test = {1};
3802 const bool is_little_endian = endianess_test.c[0] == 1; // NOLINT
3803 auto endian_select = [is_little_endian](ryml::csubstr little_endian, ryml::csubstr big_endian){
3804 return is_little_endian ? little_endian : big_endian;
3805 };
3806 //
3807 // directly encode variables: integers
3808 {
3809 const uint64_t valin = UINT64_C(0xdeadbeef);
3810 ryml::NodeRef node = tree["deadbeef"];
3812 CHECK(node.val() == endian_select("776t3gAAAAA=", "AAAAAN6tvu8="));
3813 uint64_t valout = 0;
3814 size_t len = 0;
3815 node.load(ryml::fmt::base64(valout, &len));
3816 CHECK(len == sizeof(valout));
3817 CHECK(valout == UINT64_C(0xdeadbeef)); // base64 roundtrip is bit-accurate
3818 // also works without length parameter:
3819 valout = {};
3820 node.load(ryml::fmt::base64(valout));
3821 CHECK(valout == UINT64_C(0xdeadbeef)); // base64 roundtrip is bit-accurate
3822 }
3823 // directly encode variables: floating point
3824 {
3825 const double valin = 123456.7891011;
3826 ryml::NodeRef node = tree["float"];
3828 CHECK(node.val() == endian_select("nHkooAwk/kA=", "QP4kDKAoeZw="));
3829 double valout = 0;
3830 size_t len = 0;
3831 node.load(ryml::fmt::base64(valout, &len));
3832 CHECK(len == sizeof(valout));
3833 CHECK(memcmp(&valout, &valin, sizeof(valout)) == 0); // base64 roundtrip is bit-accurate // NOLINT
3834 // also works without length parameter:
3835 valout = {};
3836 node.load(ryml::fmt::base64(valout));
3837 CHECK(memcmp(&valout, &valin, sizeof(valout)) == 0); // base64 roundtrip is bit-accurate // NOLINT
3838 }
3839 // directly encode memory ranges
3840 {
3841 const uint32_t data_in[11] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xdeadbeef};
3842 uint32_t data_out[11] = {};
3843 ryml::NodeRef node = tree["int_data"];
3844 node.set_serialized(ryml::fmt::base64(data_in, C4_COUNTOF(data_in)), ryml::VAL_PLAIN);
3845 CHECK(node.val() ==
3846 endian_select("AAAAAAEAAAACAAAAAwAAAAQAAAAFAAAABgAAAAcAAAAIAAAACQAAAO++rd4=",
3847 "AAAAAAAAAAEAAAACAAAAAwAAAAQAAAAFAAAABgAAAAcAAAAIAAAACd6tvu8="));
3848 CHECK(memcmp(data_in, data_out, sizeof(data_in)) != 0); // before the roundtrip
3849 size_t len = 0;
3850 node.load(ryml::fmt::base64(data_out, C4_COUNTOF(data_in), &len));
3851 CHECK(len == sizeof(data_out));
3852 CHECK(memcmp(data_in, data_out, sizeof(data_in)) == 0); // after the roundtrip
3853 // also works without length parameter (because data_out is an
3854 // array and not a pointer):
3855 memset(data_out, 0, sizeof(data_out));
3856 node.load(ryml::fmt::base64(data_out));
3857 CHECK(memcmp(data_in, data_out, sizeof(data_in)) == 0); // after the roundtrip
3858 }
3859}
3860
3861
3862//-----------------------------------------------------------------------------
3863// Serialization info
3864
3865/** This sample shows the main user-facing calls triggering
3866 * (de)serialization. ryml provides built-ins for all fundamental
3867 * types. For samples on how to implement other types such as STL
3868 * containers or user types, see the samples below.
3869 *
3870 * Read also the [doxygen intro to using serialization](https://rapidyaml.readthedocs.io/v0.15.2/doxygen/group__doc__serialization__using.html)
3871 */
3873{
3874 ryml::csubstr yaml = "{0: 0, 10: 10, foo: foo}";
3875 ryml::Tree tree = ryml::parse_in_arena(yaml);
3876 //
3877 //
3878 // Deserialization is done with .load() and .load_key()
3879 { int val = 1; tree[0].load(&val); CHECK(val == 0); }
3880 { unsigned val = 0; tree[1].load(&val); CHECK(val == 10); }
3881 { int key = 1; tree[0].load_key(&key); CHECK(key == 0); }
3882 { unsigned key = 0; tree[1].load_key(&key); CHECK(key == 10); }
3883 // also available in the tree:
3884 { int val = 1; tree.load(tree[0].id(), &val); CHECK(val == 0); }
3885 { unsigned val = 0; tree.load(tree[1].id(), &val); CHECK(val == 10); }
3886 { int key = 1; tree.load_key(tree[0].id(), &key); CHECK(key == 0); }
3887 { unsigned key = 0; tree.load_key(tree[1].id(), &key); CHECK(key == 10); }
3888 // .load() calls the (non returning) error callback when the
3889 // serialization fails. If you want to avoid the exceptional flow,
3890 // you can use .deserialize() / .deserialize_key(), and do not forget to
3891 // check its return status (marked as `[[nodiscard]]`):
3892 { int val = 1; CHECK(tree[0].deserialize(&val)); CHECK(val == 0); }
3893 { unsigned val = 0; CHECK(tree[1].deserialize(&val)); CHECK(val == 10); }
3894 { int key = 1; CHECK(tree[0].deserialize_key(&key)); CHECK(key == 0); }
3895 { unsigned key = 0; CHECK(tree[1].deserialize_key(&key)); CHECK(key == 10); }
3896 // also available in the tree:
3897 { int val = 1; CHECK(tree.deserialize(tree[0].id(), &val)); CHECK(val == 0); }
3898 { unsigned val = 0; CHECK(tree.deserialize(tree[1].id(), &val)); CHECK(val == 10); }
3899 { int key = 1; CHECK(tree.deserialize_key(tree[0].id(), &key)); CHECK(key == 0); }
3900 { unsigned key = 0; CHECK(tree.deserialize_key(tree[1].id(), &key)); CHECK(key == 10); }
3901 //
3902 //
3903 // Serialization is done with .save() and .save_key(). It is
3904 // carried out by converting the value to string in the tree's
3905 // arena (see sample_tree_arena()).
3906 { int val = 10; tree[0].save(val); CHECK(tree[0].val() == "10"); }
3907 { unsigned val = 11; tree[1].save(val); CHECK(tree[1].val() == "11"); }
3908 { int key = 12; tree[0].save_key(key); CHECK(tree[0].key() == "12"); }
3909 { unsigned key = 13; tree[1].save_key(key); CHECK(tree[1].key() == "13"); }
3910 // also available in the tree:
3911 { int val = 20; tree.save(tree[0].id(), val); CHECK(tree[0].val() == "20"); }
3912 { unsigned val = 21; tree.save(tree[1].id(), val); CHECK(tree[1].val() == "21"); }
3913 { int key = 22; tree.save_key(tree[0].id(), key); CHECK(tree[0].key() == "22"); }
3914 { unsigned key = 23; tree.save_key(tree[1].id(), key); CHECK(tree[1].key() == "23"); }
3915 //
3916 //
3917 // Like .load(), .save() checks the node for write-ability and
3918 // triggers an error if it failed. Likewise, to avoid the
3919 // exceptional path, you can use .set_serialized() and
3920 // .set_key_serialized():
3921 { int val = 14; tree[0].set_serialized(val); CHECK(tree[0].val() == "14"); }
3922 { unsigned val = 15; tree[1].set_serialized(val); CHECK(tree[1].val() == "15"); }
3923 { int key = 16; tree[0].set_key_serialized(key); CHECK(tree[0].key() == "16"); }
3924 { unsigned key = 17; tree[1].set_key_serialized(key); CHECK(tree[1].key() == "17"); }
3925 /// And likewise, you can use these from the tree as well:
3926 /// @ref c4::yml::Tree::set_serialized() / @ref c4::yml::Tree::set_key_serialized().
3927 { int val = 18; tree.set_serialized(tree[0].id(), val); CHECK(tree[0].val() == "18"); }
3928 { unsigned val = 19; tree.set_serialized(tree[1].id(), val); CHECK(tree[1].val() == "19"); }
3929 { int key = 20; tree.set_key_serialized(tree[0].id(), key); CHECK(tree[0].key() == "20"); }
3930 { unsigned key = 21; tree.set_key_serialized(tree[1].id(), key); CHECK(tree[1].key() == "21"); }
3931 //
3932 //
3933 // You can also (de)serialize tags and even anchors, if your
3934 // application requires it. For serialization the trick is to use
3935 // .to_arena():
3936 tree[0].set_val_tag(tree[0].to_arena(42)); CHECK(tree[0].val_tag() == "42");
3937 tree[1].set_key_tag(tree[1].to_arena(43)); CHECK(tree[1].key_tag() == "43");
3938 tree[0].set_val_anchor(tree[0].to_arena(44)); CHECK(tree[0].val_anchor() == "44");
3939 tree[1].set_key_anchor(tree[1].to_arena(45)); CHECK(tree[1].key_anchor() == "45");
3940 /// For deserialization, use from_chars().
3941 { int val = 0; CHECK(from_chars(tree[0].val_tag(), &val)); CHECK(val == 42); }
3942 { unsigned key = 0; CHECK(from_chars(tree[1].key_tag(), &key)); CHECK(key == 43); }
3943 { int val = 0; CHECK(from_chars(tree[0].val_anchor(), &val)); CHECK(val == 44); }
3944 { unsigned key = 0; CHECK(from_chars(tree[1].key_anchor(), &key)); CHECK(key == 45); }
3945}
3946
3947
3948//-----------------------------------------------------------------------------
3949// ryml uses Argument Dependent Lookup to dispatch the serialization
3950// to each type. This enables the user to implement serialization for
3951// custom types (and also enables rapidyaml to implement serialization
3952// of fundamental types).
3953//
3954// user scalar types: where nothing in the tree needs to be set.
3955// implemented in ryml through to_chars() + from_chars()
3956
3957/** @addtogroup doc_quickstart_helpers
3958 * @{ */
3959
3960/** @defgroup doc_sample_scalar_types Serialize/deserialize scalar types
3961 * @{ */
3962
3963// IMPORTANT: read the doxygen documentation for scalar serialization at:
3964// https://rapidyaml.readthedocs.io/v0.15.2/doxygen/group__doc__serialization__user__types.html
3965
3966template<class T> struct vec2 { T x, y; }; ///< example scalar type, serialized and deserialized
3967template<class T> struct vec3 { T x, y, z; }; ///< example scalar type, serialized and deserialized
3968template<class T> struct vec4 { T x, y, z, w; }; ///< example scalar type, serialized and deserialized
3969
3970template<class T> struct parse_only_vec2 { T x, y; }; ///< example scalar type, deserialized only
3971template<class T> struct parse_only_vec3 { T x, y, z; }; ///< example scalar type, deserialized only
3972template<class T> struct parse_only_vec4 { T x, y, z, w; }; ///< example scalar type, deserialized only
3973
3974template<class T> struct emit_only_vec2 { T x, y; }; ///< example scalar type, serialized only
3975template<class T> struct emit_only_vec3 { T x, y, z; }; ///< example scalar type, serialized only
3976template<class T> struct emit_only_vec4 { T x, y, z, w; }; ///< example scalar type, serialized only
3977
3978
3979// to serialize scalars, you need to define to_chars():
3980
3981template<class T> size_t to_chars(ryml::substr buf, vec2<T> v) { return ryml::format(buf, "({},{})", v.x, v.y); }
3982template<class T> size_t to_chars(ryml::substr buf, vec3<T> v) { return ryml::format(buf, "({},{},{})", v.x, v.y, v.z); }
3983template<class T> size_t to_chars(ryml::substr buf, vec4<T> v) { return ryml::format(buf, "({},{},{},{})", v.x, v.y, v.z, v.w); }
3984
3985template<class T> size_t to_chars(ryml::substr buf, emit_only_vec2<T> v) { return ryml::format(buf, "({},{})", v.x, v.y); }
3986template<class T> size_t to_chars(ryml::substr buf, emit_only_vec3<T> v) { return ryml::format(buf, "({},{},{})", v.x, v.y, v.z); }
3987template<class T> size_t to_chars(ryml::substr buf, emit_only_vec4<T> v) { return ryml::format(buf, "({},{},{},{})", v.x, v.y, v.z, v.w); }
3988
3989
3990// to deserialize scalars, you need to define from_chars():
3991
3992template<class T> bool from_chars(ryml::csubstr buf, vec2<T> *v) { size_t ret = ryml::unformat(buf, "({},{})", v->x, v->y); return ret != ryml::yml::npos; }
3993template<class T> bool from_chars(ryml::csubstr buf, vec3<T> *v) { size_t ret = ryml::unformat(buf, "({},{},{})", v->x, v->y, v->z); return ret != ryml::yml::npos; }
3994template<class T> bool from_chars(ryml::csubstr buf, vec4<T> *v) { size_t ret = ryml::unformat(buf, "({},{},{},{})", v->x, v->y, v->z, v->w); return ret != ryml::yml::npos; }
3995
3996template<class T> bool from_chars(ryml::csubstr buf, parse_only_vec2<T> *v) { size_t ret = ryml::unformat(buf, "({},{})", v->x, v->y); return ret != ryml::yml::npos; }
3997template<class T> bool from_chars(ryml::csubstr buf, parse_only_vec3<T> *v) { size_t ret = ryml::unformat(buf, "({},{},{})", v->x, v->y, v->z); return ret != ryml::yml::npos; }
3998template<class T> bool from_chars(ryml::csubstr buf, parse_only_vec4<T> *v) { size_t ret = ryml::unformat(buf, "({},{},{},{})", v->x, v->y, v->z, v->w); return ret != ryml::yml::npos; }
3999
4000
4001// IMPORTANT: read the doxygen documentation for scalar serialization at:
4002// https://rapidyaml.readthedocs.io/v0.15.2/doxygen/group__doc__serialization__user__types.html
4003
4004/** @} */ // doc_sample_scalar_types
4005/** @} */ // doc_quickstart_helpers
4006
4007
4008/** to add scalar types (ie leaf types converting to/from string),
4009 * define the functions above for those types.
4010 *
4011 * @warning read the doxygen documentation for scalar types at:
4012 * https://rapidyaml.readthedocs.io/v0.15.2/doxygen/group__doc__serialization__user__types.html
4013 *
4014 * See @ref
4015 * doc_sample_scalar_types. */
4017{
4018 ryml::Tree t;
4019
4020 auto r = t.rootref();
4021 r.set_map();
4022
4023 vec2<int> v2in{10, 11};
4024 vec2<int> v2out{1, 2};
4025 r["v2"].save(v2in); // serializes to the tree's arena, and then sets the val
4026 r["v2"].load(&v2out);
4027 CHECK(v2in.x == v2out.x);
4028 CHECK(v2in.y == v2out.y);
4029 vec3<int> v3in{100, 101, 102};
4030 vec3<int> v3out{1, 2, 3};
4031 r["v3"].save(v3in); // serializes to the tree's arena, and then sets the val
4032 r["v3"].load(&v3out);
4033 CHECK(v3in.x == v3out.x);
4034 CHECK(v3in.y == v3out.y);
4035 CHECK(v3in.z == v3out.z);
4036 vec4<int> v4in{1000, 1001, 1002, 1003};
4037 vec4<int> v4out{1, 2, 3, 4};
4038 r["v4"].save(v4in); // serializes to the tree's arena, and then sets the val
4039 r["v4"].load(&v4out);
4040 CHECK(v4in.x == v4out.x);
4041 CHECK(v4in.y == v4out.y);
4042 CHECK(v4in.z == v4out.z);
4043 CHECK(v4in.w == v4out.w);
4045 "v2: (10,11)" "\n"
4046 "v3: (100,101,102)" "\n"
4047 "v4: (1000,1001,1002,1003)" "\n"
4048 "");
4049
4050 // note that only the used functions are needed:
4051 // - if a type is only parsed, then only from_chars() is needed
4052 // - if a type is only emitted, then only to_chars() is needed
4053 emit_only_vec2<int> eov2in{20, 21}; // only has to_chars()
4054 parse_only_vec2<int> pov2out{1, 2}; // only has from_chars()
4055 r["v2"].save(eov2in); // serializes to the tree's arena, and then sets the keyval
4056 r["v2"].load(&pov2out);
4057 CHECK(eov2in.x == pov2out.x);
4058 CHECK(eov2in.y == pov2out.y);
4059 emit_only_vec3<int> eov3in{30, 31, 32}; // only has to_chars()
4060 parse_only_vec3<int> pov3out{1, 2, 3}; // only has from_chars()
4061 r["v3"].save(eov3in); // serializes to the tree's arena, and then sets the keyval
4062 r["v3"].load(&pov3out);
4063 CHECK(eov3in.x == pov3out.x);
4064 CHECK(eov3in.y == pov3out.y);
4065 CHECK(eov3in.z == pov3out.z);
4066 emit_only_vec4<int> eov4in{40, 41, 42, 43}; // only has to_chars()
4067 parse_only_vec4<int> pov4out{1, 2, 3, 4}; // only has from_chars()
4068 r["v4"].save(eov4in); // serializes to the tree's arena, and then sets the keyval
4069 r["v4"].load(&pov4out);
4070 CHECK(eov4in.x == pov4out.x);
4071 CHECK(eov4in.y == pov4out.y);
4072 CHECK(eov4in.z == pov4out.z);
4074 "v2: (20,21)" "\n"
4075 "v3: (30,31,32)" "\n"
4076 "v4: (40,41,42,43)" "\n"
4077 "");
4078}
4079
4080
4081//-----------------------------------------------------------------------------
4082// ryml uses Argument Dependent Lookup to dispatch the serialization
4083// to each type. This enables the user to implement serialization for
4084// custom types (and also enables rapidyaml to implement serialization
4085// of fundamental types).
4086
4087/** @addtogroup doc_quickstart_helpers
4088 * @{ */
4089
4090/** @defgroup doc_sample_container_types Serialize/deserialize container types
4091 * @{ */
4092
4093
4094/** example user container type: seq-like */
4095template<class T>
4097{
4098 std::vector<T> seq_member;
4099 void check_eq(my_seq_type const& that) const
4100 {
4101 CHECK(seq_member.size() == that.seq_member.size());
4102 if(seq_member.size() == that.seq_member.size())
4103 for(size_t i = 0; i < seq_member.size(); ++i)
4104 CHECK(seq_member[i] == that.seq_member[i]);
4105 }
4106};
4107/** example user container type: map-like */
4108template<class K, class V>
4110{
4111 std::map<K, V> map_member;
4112 void check_eq(my_map_type const& that) const
4113 {
4114 CHECK(map_member.size() == that.map_member.size());
4115 if(map_member.size() == that.map_member.size())
4116 {
4117 for(auto const& itthat : that.map_member)
4118 {
4119 auto it = map_member.find(itthat.first);
4120 CHECK(it != map_member.end());
4121 if(it != map_member.end())
4122 CHECK(it->second == itthat.second);
4123 }
4124 }
4125 }
4126};
4127/** example user container type with nested user types.
4128 * notice all the members have user-defined serialization methods. */
4130{
4131 // these are serialized as scalar (leaf) nodes:
4135 // these are serialized as container nodes:
4138 void check_eq(my_type const& that) const
4139 {
4140 CHECK(v2.x == that.v2.x);
4141 CHECK(v2.y == that.v2.y);
4142 CHECK(v3.x == that.v3.x);
4143 CHECK(v3.y == that.v3.y);
4144 CHECK(v3.z == that.v3.z);
4145 CHECK(v4.x == that.v4.x);
4146 CHECK(v4.y == that.v4.y);
4147 CHECK(v4.z == that.v4.z);
4148 CHECK(v4.w == that.v4.w);
4149 seq.check_eq(that.seq);
4150 map.check_eq(that.map);
4151 }
4152};
4153
4154
4155// IMPORTANT: read the doxygen documentation for deserialization of user types:
4156// https://rapidyaml.readthedocs.io/v0.15.2/doxygen/group__doc__serialization__user__types.html
4157
4158
4159// Let's first show examples of serialization (writing) functions:
4160// writing is easier because it does not have to check the data.
4161
4162// here's an example of serializing a seq type
4163template<class T>
4165{
4166 tree->set_seq(id);
4167 for(T const& v : seq.seq_member)
4168 {
4169 // inside write(), prefer using .set_serialized() instead of .save()
4170 tree->set_serialized(tree->append_child(id), v);
4171 }
4172}
4173// special optimization: if all you have are strings, AND you are sure
4174// they outlive the tree, you can avoid the copy to the tree's
4175// arena. But beware the lifetime issue!
4177{
4178 tree->set_seq(id);
4179 for(std::string const& v : seq.seq_member)
4180 {
4181 // now the tree is pointing at seq's strings. using .set_val()
4182 // does not serialize, and this avoids the copy to the tree's
4183 // arena
4184 tree->set_val(tree->append_child(id), ryml::to_csubstr(v));
4185 }
4186}
4187
4188// example of map serialization:
4189template<class K, class V>
4191{
4192 tree->set_map(id);
4193 for(auto const& v : map.map_member)
4194 {
4195 // inside write(), prefer using .set_serialized() instead of .save()
4196 ryml::id_type child_id = tree->append_child(id);
4197 tree->set_key_serialized(child_id, v.first); // we're serializing the key!
4198 tree->set_serialized(child_id, v.second);
4199 }
4200}
4201// another example of map serialization:
4202void write(ryml::Tree *tree, ryml::id_type id, my_type const& val)
4203{
4204 tree->set_map(id);
4205 // inside write(), prefer using .set_serialized() instead of .save()
4206 //
4207 ryml::id_type ch;
4208 // these are leaf nodes:
4209 ch = tree->append_child(id); tree->set_key(ch, "v2"); tree->set_serialized(ch, val.v2);
4210 ch = tree->append_child(id); tree->set_key(ch, "v3"); tree->set_serialized(ch, val.v3);
4211 ch = tree->append_child(id); tree->set_key(ch, "v4"); tree->set_serialized(ch, val.v4);
4212 // these are container nodes (note how the call is equal):
4213 ch = tree->append_child(id); tree->set_key(ch, "seq"); tree->set_serialized(ch, val.seq);
4214 ch = tree->append_child(id); tree->set_key(ch, "map"); tree->set_serialized(ch, val.map);
4215 // Note above that we're NOT serializing the keys. That works and
4216 // is correct here because the keys themselves are fixed, and are
4217 // static strings located in the executable. But if the keys came
4218 // from the data, they too would have to be serialized with
4219 // .set_key_serialized().
4220}
4221
4222
4223// Now let's implement the read() functions. Here we have more work to
4224// do because we need to check the data coming from YAML.
4225//
4226// IMPORTANT: read the doxygen documentation for deserialization of user types:
4227// https://rapidyaml.readthedocs.io/v0.15.2/doxygen/group__doc__serialization__user__types.html
4228
4229
4230template<class T>
4232{
4233 if(!tree->is_seq(id)) return ryml::ReadResult(id); // id must be a seq
4234 seq->seq_member.clear(); // we'll overwrite the vector
4235 for(ryml::id_type child = tree->first_child(id);
4236 child != ryml::NONE;
4237 child = tree->next_sibling(child))
4238 {
4239 // create a new entry
4240 seq->seq_member.emplace_back();
4241 // inside read() you SHOULD NOT use .load() because of its
4242 // exceptional flow. Instead, you should use .deserialize() to
4243 // play nice with .deserialize() callers calling this function.
4244 // Read more at the doxygen page linked above.
4245 ryml::ReadResult r = tree->deserialize(child, &seq->seq_member.back());
4246 if(!r) return r; // return the inner-most result
4247 }
4248 return ryml::ReadResult(); // all good
4249}
4250
4251template<class K, class V>
4253{
4254 if(!tree->is_map(id)) return ryml::ReadResult(id); // id must be a seq
4255 for(ryml::id_type child = tree->first_child(id);
4256 child != ryml::NONE;
4257 child = tree->next_sibling(child))
4258 {
4259 K k{};
4260 // again, we're using .deserialize() instead of .load()
4261 // because (1) we should gracefully return a ReadResult and
4262 // anyway (2) we're certain the node is readable (because
4263 // we're inside the loop).
4264 ryml::ReadResult r = tree->deserialize_key(child, &k);
4265 if(r) r = tree->deserialize(child, &map->map_member[std::move(k)]);
4266 if(!r) return r; // return the inner-most result
4267 }
4268 return ryml::ReadResult(); // all good.
4269}
4270
4271
4272//
4273// Given that ryml provides both a tree and a node API, you can
4274// implement a tree read() (as above), or a node read(). The latter
4275// will only be called if you trigger deserialization from a
4276// node. That's ok here, because that's what we're doing below.
4277//
4278// Regardless, it needs to gracefully handle bad YAML data, so we must
4279// check every step. We use .deserialize_child() which will look for a
4280// child by key, and deserialize that child, returning the deserialize
4281// result. If no such child exists, it returns a read result reporting
4282// the node (not the child).
4283//
4284// Note also how each step is defended in an if-condition on the
4285// current result. This will ensure that the first failing step is
4286// reported.
4288{
4289 ryml::ReadResult r(n.is_map(), n.id()); // node must be a map
4290 if(r) r = n.deserialize_child("v2", &val->v2);
4291 if(r) r = n.deserialize_child("v3", &val->v3);
4292 if(r) r = n.deserialize_child("v4", &val->v4);
4293 if(r) r = n.deserialize_child("seq", &val->seq);
4294 if(r) r = n.deserialize_child("map", &val->map);
4295 // hint: you can also add a default argument for when no such child exists
4296 return r;
4297}
4298// But if you're going to ever deserialize from a tree+id, then, you
4299// must implement a tree+id read(). Also, if you only implement the
4300// tree+id read(), it will get picked both from tree calls or from
4301// node calls when no node read() exists. That is the reason why you
4302// should prefer to implement the tree version. As an example, see how
4303// similar the tree version is:
4305{
4306 ryml::ReadResult r(tree->is_map(id), id); // node must be a map
4307 if(r) r = tree->deserialize_child(id, "v2", &val->v2);
4308 if(r) r = tree->deserialize_child(id, "v3", &val->v3);
4309 if(r) r = tree->deserialize_child(id, "v4", &val->v4);
4310 if(r) r = tree->deserialize_child(id, "seq", &val->seq);
4311 if(r) r = tree->deserialize_child(id, "map", &val->map);
4312 // hint: you can also add a default argument for when no such child exists
4313 return r;
4314}
4315
4316/** @} */ // doc_sample_container_types
4317
4318/** @} */ // quickstart_helpers
4319
4320
4321/** shows how to serialize/deserialize container types.
4322 *
4323 * @warning read the doxygen documentation for containers/general types at:
4324 * https://rapidyaml.readthedocs.io/v0.15.2/doxygen/group__doc__serialization__user__types.html
4325 *
4326 * @see doc_sample_container_types
4327 * @see sample_std_types
4328 * @see sample_deserialize_error
4329 * */
4331{
4332 // let's do a YAML roundtrip:
4333 ryml::Tree tree;
4334 ryml::NodeRef root_node = tree;
4335 ryml::id_type root_id = tree.root_id();
4336
4337 // here we will be doing a serialization roundtrip with a
4338 // user-defined container type.
4339 //
4340 // the read() and write() functions for this type are defined
4341 // above.
4342 const my_type orig{
4343 {20, 21},
4344 {30, 31, 32},
4345 {40, 41, 42, 43},
4346 {{101, 102, 103, 104, 105, 106, 107}},
4347 {{{1001, 2001}, {1002, 2002}, {1003, 2003}}},
4348 };
4349
4350 // serialize to the tree:
4351 root_node.save(orig);
4352 // check the YAML:
4354 "v2: (20,21)" "\n"
4355 "v3: (30,31,32)" "\n"
4356 "v4: (40,41,42,43)" "\n"
4357 "seq:" "\n"
4358 " - 101" "\n"
4359 " - 102" "\n"
4360 " - 103" "\n"
4361 " - 104" "\n"
4362 " - 105" "\n"
4363 " - 106" "\n"
4364 " - 107" "\n"
4365 "map:" "\n"
4366 " 1001: 2001" "\n"
4367 " 1002: 2002" "\n"
4368 " 1003: 2003" "\n"
4369 "");
4370 // and now let's deserialize to this variable.
4371 // first from a node:
4372 {
4373 my_type roundtrip;
4374 root_node.load(&roundtrip); // picks the node read(), because we wrote one.
4375 // if we didn't, it would then pick the tree read()
4376 roundtrip.check_eq(orig); // finally, let's compare.
4377 }
4378 // let's also deserialize from the tree:
4379 {
4380 my_type roundtrip;
4381 tree.load(root_id, &roundtrip); // will pick the tree read()
4382 roundtrip.check_eq(orig); // finally, let's compare.
4383 }
4384
4385 // We created above a my_seq<std::string> specialization showing
4386 // that we can use write() without serializing the data to the
4387 // tree's arena. Let's show it working here:
4388 const my_seq_type<std::string> strseq{{
4389 "doe",
4390 "a deer, a female deer",
4391 "ray",
4392 "a drop of golden sun"
4393 }};
4394 // serialize it to a nested node in the tree:
4395 tree["not in arena"].save(strseq);
4396 // check the YAML:
4397 CHECK(ryml::emitrs_yaml<std::string>(tree["not in arena"]) == ""
4398 // note the new elements:
4399 "not in arena:" "\n"
4400 " - doe" "\n"
4401 " - a deer, a female deer" "\n"
4402 " - ray" "\n"
4403 " - a drop of golden sun" "\n"
4404 "");
4405 // show how the strings are NOT in the tree's arena
4406 size_t pos = 0;
4407 CHECK(strseq.seq_member.size() == tree["not in arena"].num_children());
4408 for(ryml::ConstNodeRef child : tree["not in arena"].children())
4409 {
4410 ryml::csubstr str_orig = ryml::to_csubstr(strseq.seq_member[pos++]);
4411 CHECK(child.val() == str_orig); // same string
4412 CHECK(!child.val().is_sub(tree.arena())); // not in the tree's arena
4413 CHECK(child.val().is_sub(str_orig)); // ... but the original memory
4414 }
4415}
4416
4417
4418//-----------------------------------------------------------------------------
4419
4420/** shows what happens on deserialization errors
4421 *
4422 * @warning read the doxygen documentation for containers/general types at:
4423 * https://rapidyaml.readthedocs.io/v0.15.2/doxygen/group__doc__serialization__user__types.html
4424 *
4425 * @see sample_user_container_types()
4426 * @see sample_error_visit_location()
4427 * @see sample_location_tracking()
4428 */
4430{
4431 // in this example we will be checking errors, so set up a
4432 // temporary error handler to catch them:
4433 ScopedErrorHandlerExample errh; // calls ryml::set_callbacks()
4434 // let's parse this YAML containing an invalid value,
4435 // with location tracking:
4436 ryml::EventHandlerTree tree_handler;
4437 ryml::Parser parser(&tree_handler, ryml::ParserOptions{}.locations(true));
4438 const ryml::Tree tree = parse_in_arena(&parser,
4439 "v2: (20,21)" "\n"
4440 "v3: (30,31,32)" "\n"
4441 "v4: (40,41,42,43)" "\n"
4442 "seq:" "\n"
4443 " - 101" "\n"
4444 " - 102" "\n"
4445 " - 103" "\n"
4446 " - 104" "\n"
4447 " - 105" "\n"
4448 " - 106" "\n"
4449 " - 107" "\n"
4450 "map:" "\n"
4451 " 1001: 2001" "\n"
4452 " 1002: not an int" "\n" // valid YAML, will cause deserialization error
4453 " 1003: 2003" "\n"
4454 "");
4455 ryml::ConstNodeRef root = tree;
4456 // and now let's deserialize to this variable
4457 my_type var;
4458 // .deserialize() reports the error but does not trigger an error
4459 // call:
4460 ryml::ReadResult result = root.deserialize(&var);
4461 CHECK(!result);
4462 CHECK(result.node == tree["map"]["1002"].id()); // this is where the error occurred
4463 CHECK(tree.location(parser, result.node).line == 13);
4464 CHECK(tree.location(parser, result.node).col == 2); // this node starts a column 2
4465 CHECK(parser.val_location(tree.val(result.node).str).col == 8); // its value starts at column 8
4466 // and .load() will trigger a visit error, reporting the node as
4467 // well. see sample_error_visit_location() for examples on how to
4468 // track the location of a visit error
4469 errh.check_error_occurs([&]{ root.load(&var); });
4470}
4471
4472
4473//-----------------------------------------------------------------------------
4474
4475/** demonstrates usage with the std implementations provided by ryml
4476 in the ryml_std.hpp header
4477 @see @ref doc_sample_container_types
4478 @see also the STL section in @ref doc_serialization */
4480{
4481 // we're using C-strings because doxygen breaks down on raw strings
4482 std::string yml_std_string = ""
4483 "- v2: (20,21)" "\n"
4484 " v3: (30,31,32)" "\n"
4485 " v4: (40,41,42,43)" "\n"
4486 " seq:" "\n"
4487 " - 101" "\n"
4488 " - 102" "\n"
4489 " - 103" "\n"
4490 " - 104" "\n"
4491 " - 105" "\n"
4492 " - 106" "\n"
4493 " - 107" "\n"
4494 " map:" "\n"
4495 " 1001: 2001" "\n"
4496 " 1002: 2002" "\n"
4497 " 1003: 2003" "\n"
4498 "- v2: (120,121)" "\n"
4499 " v3: (130,131,132)" "\n"
4500 " v4: (140,141,142,143)" "\n"
4501 " seq:" "\n"
4502 " - 1101" "\n"
4503 " - 1102" "\n"
4504 " - 1103" "\n"
4505 " - 1104" "\n"
4506 " - 1105" "\n"
4507 " - 1106" "\n"
4508 " - 1107" "\n"
4509 " map:" "\n"
4510 " 11001: 12001" "\n"
4511 " 11002: 12002" "\n"
4512 " 11003: 12003" "\n"
4513 "- v2: (220,221)" "\n"
4514 " v3: (230,231,232)" "\n"
4515 " v4: (240,241,242,243)" "\n"
4516 " seq:" "\n"
4517 " - 2101" "\n"
4518 " - 2102" "\n"
4519 " - 2103" "\n"
4520 " - 2104" "\n"
4521 " - 2105" "\n"
4522 " - 2106" "\n"
4523 " - 2107" "\n"
4524 " map:" "\n"
4525 " 21001: 22001" "\n"
4526 " 21002: 22002" "\n"
4527 " 21003: 22003" "\n"
4528 "";
4529 // parse in-place using the std::string above
4530 ryml::Tree tree = ryml::parse_in_place(ryml::to_substr(yml_std_string));
4531 // my_type is a container-of-containers type. see above its
4532 // definition implementation for ryml.
4533 std::vector<my_type> vec;
4534 tree.rootref().load(&vec);
4535 CHECK(vec.size() == 3);
4536 ryml::Tree tree_out;
4537 tree_out.rootref().save(vec);
4538 CHECK(ryml::emitrs_yaml<std::string>(tree_out) == yml_std_string);
4539}
4540
4541
4542//-----------------------------------------------------------------------------
4543
4544/** control precision of serialized floats */
4546{
4547 std::vector<double> reference{1.23234412342131234, 2.12323123143434237, 3.67847983572591234};
4548 // A safe precision for comparing doubles. May vary depending on
4549 // compiler flags. Double goes to about 15 digits, so 14 should be
4550 // safe enough for this test to succeed.
4551 const double precision_safe = 1.e-14;
4552 const size_t num_digits_safe = 14;
4553 const size_t num_digits_original = 17;
4554 auto get_num_digits = [](ryml::csubstr number){ return number.len - 2u; };
4555 //
4556 // no significant precision is lost when reading
4557 // floating point numbers:
4558 {
4559 ryml::Tree tree = ryml::parse_in_arena("[1.23234412342131234, 2.12323123143434237, 3.67847983572591234]");
4560 std::vector<double> output;
4561 tree.rootref().load(&output);
4562 CHECK(output.size() == reference.size());
4563 for(size_t i = 0; i < reference.size(); ++i)
4564 {
4565 CHECK(get_num_digits(tree[(ryml::id_type)i].val()) == num_digits_original);
4566 CHECK(fabs(output[i] - reference[i]) < precision_safe);
4567 }
4568 }
4569 //
4570 // However, depending on the compilation settings, there may be a
4571 // significant precision loss when serializing with the default
4572 // approach, .save(double):
4573 {
4574 ryml::Tree serialized;
4575 serialized.rootref().save(reference);
4576 // Without std::to_chars() there is a loss of precision:
4577 #if (!C4CORE_HAVE_STD_TOCHARS) // check if std::to_chars() is available.
4578 CHECK((ryml::emitrs_yaml<std::string>(serialized) == ""
4579 "- 1.23234" "\n"
4580 "- 2.12323" "\n"
4581 "- 3.67848" "\n"
4582 "") || (bool)"this is indicative; the exact results will vary from platform to platform.");
4583 C4_UNUSED(num_digits_safe);
4584 // ... but when using C++17 and above, the results are eminently equal:
4585 #else
4586 CHECK((ryml::emitrs_yaml<std::string>(serialized) == ""
4587 "- 1.2323441234213124" "\n"
4588 "- 2.1232312314343424" "\n"
4589 "- 3.6784798357259123" "\n"
4590 "") || (bool)"this is indicative; the exact results will vary from platform to platform.");
4591 size_t pos = 0;
4592 for(ryml::ConstNodeRef child : serialized.rootref().children())
4593 {
4594 CHECK(get_num_digits(child.val()) >= num_digits_safe);
4595 double out = {};
4596 child.load(&out);
4597 CHECK(fabs(out - reference[pos++]) < precision_safe);
4598 }
4599 #endif
4600 }
4601 //
4602 // The difference is explained by the availability of
4603 // fastfloat::from_chars(), std::from_chars() and std::to_chars().
4604 //
4605 // ryml prefers the fastfloat::from_chars() version. Unfortunately
4606 // fastfloat does not have to_chars() (see
4607 // https://github.com/fastfloat/fast_float/issues/23).
4608 //
4609 // When C++17 is used, ryml uses std::to_chars(), which produces
4610 // good defaults.
4611 //
4612 // However, with earlier standards, or in some library
4613 // implementations, there's only snprintf() available. Every other
4614 // std library function will either disrespect the string limits,
4615 // or more precisely, accept no string size limits. So the
4616 // implementation of c4core (which ryml uses) falls back to
4617 // snprintf("%g"), and that picks by default a (low) number of
4618 // digits.
4619 //
4620 // But not all is lost for C++11/C++14 users!
4621 //
4622 // To force a particular precision when serializing, you can use
4623 // c4::fmt::real() (brought into the ryml:: namespace). Or you can
4624 // serialize the number yourself! The small downside is that you
4625 // have to build the container.
4626 //
4627 // First a function to check the result:
4628 auto check_precision = [&](ryml::Tree const& serialized){
4629 std::cout << serialized;
4630 // now it works!
4631 CHECK((ryml::emitrs_yaml<std::string>(serialized) == ""
4632 "- 1.23234412342131239" "\n"
4633 "- 2.12323123143434245" "\n"
4634 "- 3.67847983572591231" "\n"
4635 "") || (bool)"this is indicative; the exact results will vary from platform to platform.");
4636 size_t pos = 0;
4637 for(ryml::ConstNodeRef child : serialized.rootref().children())
4638 {
4639 CHECK(get_num_digits(child.val()) == num_digits_original);
4640 double out = {};
4641 child.load(&out);
4642 CHECK(fabs(out - reference[pos++]) < precision_safe);
4643 }
4644 };
4645 //
4646 // Serialization example using fmt::real()
4647 {
4648 ryml::Tree serialized;
4649 ryml::NodeRef root = serialized.rootref();
4650 root.set_seq();
4651 for(const double v : reference)
4652 root.append_child().save(ryml::fmt::real(v, num_digits_original, ryml::FTOA_FLOAT));
4653 check_precision(serialized); // OK - now within bounds!
4654 }
4655 //
4656 // Serialization example using snprintf
4657 {
4658 ryml::Tree serialized;
4659 ryml::NodeRef root = serialized.rootref();
4660 root.set_seq();
4661 char tmp[64];
4662 for(const double v : reference)
4663 {
4664 // reuse a buffer to serialize.
4665 // add 1 to the significant digits because the %g
4666 // specifier counts the integral digits.
4667 (void)snprintf(tmp, sizeof(tmp), "%.18g", v);
4668 // copy the serialized string to the tree (.save()
4669 // copies to the arena, .set_val() just assigns the string
4670 // pointer and would be wrong in this case):
4671 root.append_child().save(ryml::to_csubstr((const char*)tmp));
4672 }
4673 check_precision(serialized); // OK - now within bounds!
4674 }
4675}
4676
4677
4678//-----------------------------------------------------------------------------
4679
4680/** demonstrates how to emit to a linear container of char */
4682{
4683 ryml::csubstr ymla =
4684 "- 1\n"
4685 "- 2\n"
4686 "";
4687 ryml::csubstr ymlb =
4688 "- a" "\n"
4689 "- b" "\n"
4690 "- x0: 1" "\n"
4691 " x1: 2" "\n"
4692 "- champagne: Dom Perignon" "\n"
4693 " coffee: Arabica" "\n"
4694 " more:" "\n"
4695 " vinho verde: Soalheiro" "\n"
4696 " vinho tinto: Redoma 2017" "\n"
4697 " beer:" "\n"
4698 " - Rochefort 10" "\n"
4699 " - Busch" "\n"
4700 " - Leffe Rituel" "\n"
4701 "- foo" "\n"
4702 "- bar" "\n"
4703 "- baz" "\n"
4704 "- bat" "\n"
4705 "";
4706 const ryml::Tree treea = ryml::parse_in_arena(ymla);
4707 const ryml::Tree treeb = ryml::parse_in_arena(ymlb);
4708
4709 // it is possible to emit to any linear container of char.
4710 //
4711 // eg, std::vector<char>
4712 {
4713 // do a blank call on an empty buffer to find the required size.
4714 // no overflow will occur, and returns a substr with the size
4715 // required to output
4716 ryml::csubstr output = ryml::emit_yaml(treea, treea.root_id(), ryml::substr{}, /*error_on_excess*/false);
4717 CHECK(output.str == nullptr);
4718 CHECK(output.len > 0);
4719 size_t num_needed_chars = output.len;
4720 std::vector<char> buf(num_needed_chars);
4721 // now try again with the proper buffer
4722 output = ryml::emit_yaml(treea, treea.root_id(), ryml::to_substr(buf), /*error_on_excess*/true);
4723 CHECK(output == ymla);
4724
4725 // it is possible to reuse the buffer and grow it as needed.
4726 // first do a blank run to find the size:
4727 output = ryml::emit_yaml(treeb, treeb.root_id(), ryml::substr{}, /*error_on_excess*/false);
4728 CHECK(output.str == nullptr);
4729 CHECK(output.len > 0);
4730 CHECK(output.len == ymlb.len);
4731 num_needed_chars = output.len;
4732 buf.resize(num_needed_chars);
4733 // now try again with the proper buffer
4734 output = ryml::emit_yaml(treeb, treeb.root_id(), ryml::to_substr(buf), /*error_on_excess*/true);
4735 CHECK(output == ymlb);
4736
4737 // there is a convenience wrapper performing the same as above:
4738 // provided to_substr() is defined for that container.
4739 output = ryml::emitrs_yaml(treeb, &buf);
4740 CHECK(output == ymlb);
4741
4742 // or you can just output a new container:
4743 // provided to_substr() is defined for that container.
4744 std::vector<char> another = ryml::emitrs_yaml<std::vector<char>>(treeb);
4745 CHECK(ryml::to_csubstr(another) == ymlb);
4746
4747 // you can also emit nested nodes:
4748 another = ryml::emitrs_yaml<std::vector<char>>(treeb[3][2]);
4749 CHECK(ryml::to_csubstr(another) == ""
4750 "more:" "\n"
4751 " vinho verde: Soalheiro" "\n"
4752 " vinho tinto: Redoma 2017" "\n"
4753 "");
4754 }
4755
4756
4757 // eg, std::string. notice this is the same code as above
4758 {
4759 // do a blank call on an empty buffer to find the required size.
4760 // no overflow will occur, and returns a substr with the size
4761 // required to output
4762 ryml::csubstr output = ryml::emit_yaml(treea, treea.root_id(), ryml::substr{}, /*error_on_excess*/false);
4763 CHECK(output.str == nullptr);
4764 CHECK(output.len > 0);
4765 size_t num_needed_chars = output.len;
4766 std::string buf;
4767 buf.resize(num_needed_chars);
4768 // now try again with the proper buffer
4769 output = ryml::emit_yaml(treea, treea.root_id(), ryml::to_substr(buf), /*error_on_excess*/true);
4770 CHECK(output == ymla);
4771
4772 // it is possible to reuse the buffer and grow it as needed.
4773 // first do a blank run to find the size:
4774 output = ryml::emit_yaml(treeb, treeb.root_id(), ryml::substr{}, /*error_on_excess*/false);
4775 CHECK(output.str == nullptr);
4776 CHECK(output.len > 0);
4777 CHECK(output.len == ymlb.len);
4778 num_needed_chars = output.len;
4779 buf.resize(num_needed_chars);
4780 // now try again with the proper buffer
4781 output = ryml::emit_yaml(treeb, treeb.root_id(), ryml::to_substr(buf), /*error_on_excess*/true);
4782 CHECK(output == ymlb);
4783
4784 // there is a convenience wrapper performing the above instructions:
4785 // provided to_substr() is defined for that container
4786 output = ryml::emitrs_yaml(treeb, &buf);
4787 CHECK(output == ymlb);
4788
4789 // or you can just output a new container:
4790 // provided to_substr() is defined for that container.
4791 std::string another = ryml::emitrs_yaml<std::string>(treeb);
4792 CHECK(ryml::to_csubstr(another) == ymlb);
4793
4794 // you can also emit nested nodes:
4795 another = ryml::emitrs_yaml<std::string>(treeb[3][2]);
4796 CHECK(ryml::to_csubstr(another) == ""
4797 "more:" "\n"
4798 " vinho verde: Soalheiro" "\n"
4799 " vinho tinto: Redoma 2017" "\n"
4800 "");
4801 }
4802}
4803
4804
4805//-----------------------------------------------------------------------------
4806
4807/** demonstrates how to emit to a stream-like structure */
4809{
4810 ryml::csubstr ymlb =
4811 "- a" "\n"
4812 "- b" "\n"
4813 "- x0: 1" "\n"
4814 " x1: 2" "\n"
4815 "- champagne: Dom Perignon" "\n"
4816 " coffee: Arabica" "\n"
4817 " more:" "\n"
4818 " vinho verde: Soalheiro" "\n"
4819 " vinho tinto: Redoma 2017" "\n"
4820 " beer:" "\n"
4821 " - Rochefort 10" "\n"
4822 " - Busch" "\n"
4823 " - Leffe Rituel" "\n"
4824 "- foo" "\n"
4825 "- bar" "\n"
4826 "- baz" "\n"
4827 "- bat" "\n"
4828 "";
4829 const ryml::Tree tree = ryml::parse_in_arena(ymlb);
4830
4831 std::string s;
4832
4833 // emit a full tree
4834 {
4835 std::stringstream ss;
4836 ss << tree; // works with any stream having .operator<<() and .write()
4837 s = ss.str();
4838 CHECK(ryml::to_csubstr(s) == ymlb);
4839 }
4840
4841 // emit a full tree as json
4842 {
4843 std::stringstream ss;
4844 ss << ryml::as_json(tree); // works with any stream having .operator<<() and .write()
4845 s = ss.str();
4846 CHECK(s ==
4847 "[" "\n"
4848 " \"a\"," "\n"
4849 " \"b\"," "\n"
4850 " {" "\n"
4851 " \"x0\": 1," "\n"
4852 " \"x1\": 2" "\n"
4853 " }," "\n"
4854 " {" "\n"
4855 " \"champagne\": \"Dom Perignon\"," "\n"
4856 " \"coffee\": \"Arabica\"," "\n"
4857 " \"more\": {" "\n"
4858 " \"vinho verde\": \"Soalheiro\"," "\n"
4859 " \"vinho tinto\": \"Redoma 2017\"" "\n"
4860 " }," "\n"
4861 " \"beer\": [" "\n"
4862 " \"Rochefort 10\"," "\n"
4863 " \"Busch\"," "\n"
4864 " \"Leffe Rituel\"" "\n"
4865 " ]" "\n"
4866 " }," "\n"
4867 " \"foo\"," "\n"
4868 " \"bar\"," "\n"
4869 " \"baz\"," "\n"
4870 " \"bat\"" "\n"
4871 "]" "\n"
4872 "");
4873 }
4874
4875 // emit a nested node
4876 {
4877 std::stringstream ss;
4878 ss << tree[3][2]; // works with any stream having .operator<<() and .write()
4879 s = ss.str();
4880 CHECK(s == ""
4881 "more:" "\n"
4882 " vinho verde: Soalheiro" "\n"
4883 " vinho tinto: Redoma 2017" "\n"
4884 "");
4885 }
4886
4887 // emit a nested node as json
4888 {
4889 std::stringstream ss;
4890 ss << ryml::as_json(tree[3][2]); // works with any stream having .operator<<() and .write()
4891 s = ss.str();
4893 "\"more\": {" "\n"
4894 " \"vinho verde\": \"Soalheiro\"," "\n"
4895 " \"vinho tinto\": \"Redoma 2017\"" "\n"
4896 "}" "\n"
4897 "");
4898 }
4899}
4900
4901
4902//-----------------------------------------------------------------------------
4903
4904/** demonstrates how to emit to a FILE* */
4906{
4907 ryml::csubstr yml = ""
4908 "- a" "\n"
4909 "- b" "\n"
4910 "- x0: 1" "\n"
4911 " x1: 2" "\n"
4912 "- champagne: Dom Perignon" "\n"
4913 " coffee: Arabica" "\n"
4914 " more:" "\n"
4915 " vinho verde: Soalheiro" "\n"
4916 " vinho tinto: Redoma 2017" "\n"
4917 " beer:" "\n"
4918 " - Rochefort 10" "\n"
4919 " - Busch" "\n"
4920 " - Leffe Rituel" "\n"
4921 "- foo" "\n"
4922 "- bar" "\n"
4923 "- baz" "\n"
4924 "- bat" "\n"
4925 "";
4926 const ryml::Tree tree = ryml::parse_in_arena(yml);
4927 // this is emitting to stdout, but of course you can pass in any
4928 // FILE* obtained from fopen()
4929 ryml::emit_yaml(tree, tree.root_id(), stdout);
4930}
4931
4932
4933//-----------------------------------------------------------------------------
4934
4935/** just like parsing into a nested node, you can also emit from a nested node. */
4937{
4938 const ryml::Tree tree = ryml::parse_in_arena(""
4939 "- a" "\n"
4940 "- b" "\n"
4941 "- x0: 1" "\n"
4942 " x1: 2" "\n"
4943 "- champagne: Dom Perignon" "\n"
4944 " coffee: Arabica" "\n"
4945 " more:" "\n"
4946 " vinho verde: Soalheiro" "\n"
4947 " vinho tinto: Redoma 2017" "\n"
4948 " beer:" "\n"
4949 " - Rochefort 10" "\n"
4950 " - Busch" "\n"
4951 " - Leffe Rituel" "\n"
4952 " - - and so" "\n"
4953 " - many other" "\n"
4954 " - wonderful beers" "\n"
4955 "- more" "\n"
4956 "- seq" "\n"
4957 "- members" "\n"
4958 "- here" "\n"
4959 "");
4960 // Let's now emit the beer node. Note that its key is also
4961 // emitted, making the result a map with a single child:
4962 CHECK(ryml::emitrs_yaml<std::string>(tree[3]["beer"]) == ""
4963 "beer:" "\n"
4964 " - Rochefort 10" "\n"
4965 " - Busch" "\n"
4966 " - Leffe Rituel" "\n"
4967 " - - and so" "\n"
4968 " - many other" "\n"
4969 " - wonderful beers" "\n"
4970 "");
4971 // You can use EmitOptions to prevent the key from being
4972 // emitted. Note how the result is now just the contents without
4973 // the key, and therefore the root is now a seq:
4974 auto without_key = ryml::EmitOptions{}.emit_nonroot_key(false);
4975 CHECK(ryml::emitrs_yaml<std::string>(tree[3]["beer"], without_key) == ""
4976 "- Rochefort 10" "\n"
4977 "- Busch" "\n"
4978 "- Leffe Rituel" "\n"
4979 "- - and so" "\n"
4980 " - many other" "\n"
4981 " - wonderful beers" "\n"
4982 "");
4983 // For sequences, the behavior is opposite. Notice how the leading
4984 // dash defaults to omit:
4985 CHECK(ryml::emitrs_yaml<std::string>(tree[3]["beer"][3]) == ""
4986 "- and so" "\n"
4987 "- many other" "\n"
4988 "- wonderful beers" "\n"
4989 "");
4990 // And likewise, you can use EmitOptions to force the dash:
4991 auto with_dash = ryml::EmitOptions{}.emit_nonroot_dash(true);
4992 CHECK(ryml::emitrs_yaml<std::string>(tree[3]["beer"][3], with_dash) == ""
4993 "- - and so" "\n" // note the added dash and indentation
4994 " - many other" "\n"
4995 " - wonderful beers" "\n"
4996 "");
4997 // Example: emit a scalar node (seq member):
4998 CHECK(ryml::emitrs_yaml<std::string>(tree[3]["beer"][0]) == "Rochefort 10");
4999 CHECK(ryml::emitrs_yaml<std::string>(tree[3]["beer"][0], with_dash) == "- Rochefort 10\n");
5000 // Example: emit a scalar node (map member):
5001 CHECK(ryml::emitrs_yaml<std::string>(tree[3]["more"][0]) == "vinho verde: Soalheiro\n");
5002 CHECK(ryml::emitrs_yaml<std::string>(tree[3]["more"][0], without_key) == "Soalheiro");
5003}
5004
5005
5006//-----------------------------------------------------------------------------
5007
5008/** query/set/modify node style to control formatting of emitted YAML
5009 * code.
5010 *
5011 * See also:
5012 * - @ref sample_parse_style()
5013 * - @ref sample_create_tree_style()
5014 * - @ref sample_style_flow_formatting()
5015 * - @ref sample_style_flow_ml_indent()
5016 * */
5018{
5019 // we will be using these helpers throughout this function
5020 auto tostr = [](ryml::ConstNodeRef n) {
5022 };
5023 auto tostr_opts = [](ryml::ConstNodeRef n, ryml::EmitOptions opts) {
5024 return ryml::emitrs_yaml<std::string>(n, opts);
5025 };
5026 // let's parse this yaml:
5027 ryml::csubstr yaml = ""
5028 "block map:" "\n"
5029 " block key: block val" "\n"
5030 "block seq:" "\n"
5031 " - block val 1" "\n"
5032 " - block val 2" "\n"
5033 " - 'quoted'" "\n"
5034 "flow map, singleline: {flow key: flow val}" "\n"
5035 "flow seq, singleline: [flow val,flow val]" "\n"
5036 "flow map, multiline: {" "\n"
5037 " flow key: flow val" "\n"
5038 " }" "\n"
5039 "flow seq, multiline: [" "\n"
5040 " flow val," "\n"
5041 " flow val" "\n"
5042 " ]" "\n"
5043 "";
5044 ryml::Tree tree = ryml::parse_in_arena(yaml);
5045 // while parsing, ryml marks parsed nodes with their original style:
5046 CHECK(tree.rootref().is_block());
5047 CHECK(tree["block map"].is_key_plain());
5048 CHECK(tree["block seq"].is_key_plain());
5049 CHECK(tree["flow map, singleline"].is_key_plain());
5050 CHECK(tree["flow seq, singleline"].is_key_plain());
5051 CHECK(tree["flow map, multiline"].is_key_plain());
5052 CHECK(tree["flow seq, multiline"].is_key_plain());
5053 CHECK(tree["block map"].is_block());
5054 CHECK(tree["block seq"].is_block());
5055 // flow is either singleline (FLOW_SL) or multiline (FLOW_ML1)
5056 CHECK(tree["flow map, singleline"].is_flow_sl());
5057 CHECK(tree["flow seq, singleline"].is_flow_sl());
5058 CHECK(tree["flow map, multiline"].is_flow_ml1());
5059 CHECK(tree["flow seq, multiline"].is_flow_ml1());
5060 // is_flow() is equivalent to (is_flow_sl() || is_flow_ml1() || is_flow_mln())
5061 CHECK(tree["flow map, singleline"].is_flow());
5062 CHECK(tree["flow seq, singleline"].is_flow());
5063 CHECK(tree["flow map, multiline"].is_flow());
5064 CHECK(tree["flow seq, multiline"].is_flow());
5065 //
5066 // since the tree nodes are marked with their original parsed
5067 // style, emitting the parsed tree will preserve the original
5068 // style (minus whitespace):
5069 //
5070 CHECK(tostr(tree) == yaml); // same as before!
5071 //
5072 // you can set/modify the style programatically!
5073 //
5074 // here are more examples.
5075 //
5076 {
5077 ryml::NodeRef n = tree["block map"]; // Let's look at one node
5078 // It looks like this originally:
5079 CHECK(tostr(n) ==
5080 "block map:\n"
5081 " block key: block val\n"
5082 "");
5083 // let's modify its style:
5084 n.set_key_style(ryml::KEY_SQUO); // scalar style: to single-quoted scalar
5085 n.set_container_style(ryml::FLOW_SL); // container style: to flow singleline
5086 // now it looks like this:
5087 CHECK(tostr(n) ==
5088 "'block map': {block key: block val}\n"
5089 "");
5090 }
5091 // next example
5092 {
5093 ryml::NodeRef n = tree["block seq"];
5094 CHECK(tostr(n) == ""
5095 "block seq:\n"
5096 " - block val 1\n"
5097 " - block val 2\n"
5098 " - 'quoted'\n"
5099 "");
5100 n.set_key_style(ryml::KEY_DQUO); // scalar style: to double-quoted scalar
5101 n[2].set_val_style(ryml::VAL_PLAIN); // scalar style: to plain
5102 n.set_container_style(ryml::FLOW_MLN); // container style: to flow multiline, N values per line
5103 CHECK(tostr(n) == ""
5104 "\"block seq\": [\n"
5105 " block val 1,block val 2,quoted\n"
5106 " ]\n");
5107 n.set_container_style(ryml::FLOW_MLN|ryml::FLOW_SPC); // force space after comma
5108 CHECK(tostr(n) == ""
5109 "\"block seq\": [\n"
5110 " block val 1, block val 2, quoted\n"
5111 " ]\n");
5112 auto maxcols20 = ryml::EmitOptions{}.max_cols(20); // set the max number of cols for FLOW_MLN
5113 CHECK(tostr_opts(n, maxcols20) == ""
5114 "\"block seq\": [\n"
5115 " block val 1, block val 2,\n"
5116 " quoted\n"
5117 " ]\n");
5118 n.set_container_style(ryml::FLOW_MLN); // no spaces now
5119 CHECK(tostr_opts(n, maxcols20) == ""
5120 "\"block seq\": [\n"
5121 " block val 1,block val 2,\n"
5122 " quoted\n"
5123 " ]\n");
5124 n.set_container_style(ryml::FLOW_SL); // to flow singleline
5125 CHECK(tostr(n) == ""
5126 "\"block seq\": [block val 1,block val 2,quoted]\n");
5127 n.set_container_style(ryml::FLOW_SL|ryml::FLOW_SPC); // now with space after comma
5128 CHECK(tostr(n) == ""
5129 "\"block seq\": [block val 1, block val 2, quoted]\n");
5130 n.set_container_style(ryml::FLOW_ML1); // to flow multiline, 1 value per line
5131 CHECK(tostr(n) == ""
5132 "\"block seq\": [\n"
5133 " block val 1,\n"
5134 " block val 2,\n"
5135 " quoted\n"
5136 " ]\n");
5137 /// @see See more details about formatting flow containers in
5138 /// @ref sample_style_flow_formatting() (below).
5139 }
5140 // next example
5141 {
5142 ryml::NodeRef n = tree["flow map, singleline"];
5143 CHECK(tostr(n) == "flow map, singleline: {flow key: flow val}\n");
5145 n["flow key"].set_val_style(ryml::VAL_LITERAL);
5146 CHECK(tostr(n) == ""
5147 "flow map, singleline:\n"
5148 " flow key: |-\n"
5149 " flow val\n"
5150 "");
5151 }
5152 // next example
5153 {
5154 ryml::NodeRef n = tree["flow map, multiline"];
5155 CHECK(tostr(n) == ""
5156 "flow map, multiline: {\n"
5157 " flow key: flow val\n"
5158 " }\n"
5159 "");
5161 CHECK(tostr(n) == ""
5162 "flow map, multiline:\n"
5163 " flow key: flow val\n"
5164 "");
5165 }
5166 // next example
5167 {
5168 ryml::NodeRef n = tree["flow seq, singleline"];
5169 CHECK(tostr(n) == "flow seq, singleline: [flow val,flow val]\n");
5174 CHECK(tostr(n) == ""
5175 "? >-\n"
5176 " flow seq, singleline\n"
5177 ":\n"
5178 " - 'flow val'\n"
5179 " - \"flow val\"\n"
5180 "");
5181 }
5182 // next example
5183 {
5184 ryml::NodeRef n = tree["flow seq, multiline"];
5185 CHECK(tostr(n) == ""
5186 "flow seq, multiline: [\n"
5187 " flow val,\n"
5188 " flow val\n"
5189 " ]\n"
5190 "");
5192 CHECK(tostr(n) == "flow seq, multiline: [flow val,flow val]\n");
5193 }
5194 // note the full tree now:
5195 CHECK(tostr(tree) != yaml);
5196 CHECK(tostr(tree) ==
5197 "'block map': {block key: block val}" "\n"
5198 "\"block seq\": [" "\n"
5199 " block val 1," "\n"
5200 " block val 2," "\n"
5201 " quoted" "\n"
5202 " ]" "\n"
5203 "flow map, singleline:" "\n"
5204 " flow key: |-" "\n"
5205 " flow val" "\n"
5206 "? >-" "\n"
5207 " flow seq, singleline" "\n"
5208 ":" "\n"
5209 " - 'flow val'" "\n"
5210 " - \"flow val\"" "\n"
5211 "flow map, multiline:" "\n"
5212 " flow key: flow val" "\n"
5213 "flow seq, multiline: [flow val,flow val]" "\n"
5214 "");
5215 // you can clear the style of single nodes:
5216 tree["block map"].clear_style();
5217 tree["block seq"].clear_style();
5218 CHECK(tostr(tree) ==
5219 "block map:" "\n"
5220 " block key: block val" "\n"
5221 "block seq:" "\n"
5222 " - block val 1" "\n"
5223 " - block val 2" "\n"
5224 " - quoted" "\n"
5225 "flow map, singleline:" "\n"
5226 " flow key: |-" "\n"
5227 " flow val" "\n"
5228 "? >-" "\n"
5229 " flow seq, singleline" "\n"
5230 ":" "\n"
5231 " - 'flow val'" "\n"
5232 " - \"flow val\"" "\n"
5233 "flow map, multiline:" "\n"
5234 " flow key: flow val" "\n"
5235 "flow seq, multiline: [flow val,flow val]" "\n"
5236 "");
5237 // you can clear the style recursively:
5238 tree.rootref().clear_style(/*recurse*/true);
5239 // when emitting nodes which have no style set, ryml will default
5240 // to block format for containers, and call
5241 // ryml::scalar_style_choose() to pick the style for each scalar
5242 // (at the cost of a scan over each scalar). Note that ryml picks
5243 // single-quoted for scalars containing commas:
5244 CHECK(tostr(tree) ==
5245 "block map:" "\n"
5246 " block key: block val" "\n"
5247 "block seq:" "\n"
5248 " - block val 1" "\n"
5249 " - block val 2" "\n"
5250 " - quoted" "\n"
5251 "flow map, singleline:" "\n"
5252 " flow key: flow val" "\n"
5253 "flow seq, singleline:" "\n"
5254 " - flow val" "\n"
5255 " - flow val" "\n"
5256 "flow map, multiline:" "\n"
5257 " flow key: flow val" "\n"
5258 "flow seq, multiline:" "\n"
5259 " - flow val" "\n"
5260 " - flow val" "\n"
5261 "");
5262 // you can set the style based on type conditions:
5263 //
5264 // eg, set a single key to single-quoted
5265 tree["block map"].set_style_conditionally(/*type_mask*/ryml::KEY,
5266 /*remflags*/ryml::KEY_STYLE,
5267 /*addflags*/ryml::KEY_SQUO,
5268 /*recurse*/false);
5269 CHECK(tostr(tree) ==
5270 "'block map':" "\n"
5271 " block key: block val" "\n"
5272 "block seq:" "\n"
5273 " - block val 1" "\n"
5274 " - block val 2" "\n"
5275 " - quoted" "\n"
5276 "flow map, singleline:" "\n"
5277 " flow key: flow val" "\n"
5278 "flow seq, singleline:" "\n"
5279 " - flow val" "\n"
5280 " - flow val" "\n"
5281 "flow map, multiline:" "\n"
5282 " flow key: flow val" "\n"
5283 "flow seq, multiline:" "\n"
5284 " - flow val" "\n"
5285 " - flow val" "\n"
5286 "");
5287 // change all keys to single-quoted:
5288 tree.rootref().set_style_conditionally(/*type_mask*/ryml::KEY,
5289 /*remflags*/ryml::KEY_STYLE,
5290 /*addflags*/ryml::KEY_SQUO,
5291 /*recurse*/true);
5292 // change all vals to double-quoted
5293 tree.rootref().set_style_conditionally(/*type_mask*/ryml::VAL,
5294 /*remflags*/ryml::VAL_STYLE,
5295 /*addflags*/ryml::VAL_DQUO,
5296 /*recurse*/true);
5297 // change all seqs to flow
5298 tree.rootref().set_style_conditionally(/*type_mask*/ryml::SEQ,
5299 /*remflags*/ryml::CONTAINER_STYLE,
5300 /*addflags*/ryml::FLOW_SL,
5301 /*recurse*/true);
5302 // change all maps to flow
5303 tree.rootref().set_style_conditionally(/*type_mask*/ryml::MAP,
5304 /*remflags*/ryml::CONTAINER_STYLE,
5305 /*addflags*/ryml::BLOCK,
5306 /*recurse*/true);
5307 // done!
5308 CHECK(tostr(tree) == ""
5309 "'block map':" "\n"
5310 " 'block key': \"block val\"" "\n"
5311 "'block seq': [\"block val 1\",\"block val 2\",\"quoted\"]" "\n"
5312 "'flow map, singleline':" "\n"
5313 " 'flow key': \"flow val\"" "\n"
5314 "'flow seq, singleline': [\"flow val\",\"flow val\"]" "\n"
5315 "'flow map, multiline':" "\n"
5316 " 'flow key': \"flow val\"" "\n"
5317 "'flow seq, multiline': [\"flow val\",\"flow val\"]" "\n"
5318 "");
5319 // you can also set a conditional style in a single node (or its branch if recurse is true):
5320 tree["flow seq, singleline"].set_style_conditionally(/*type_mask*/ryml::SEQ,
5321 /*remflags*/ryml::CONTAINER_STYLE,
5322 /*addflags*/ryml::BLOCK,
5323 /*recurse*/false);
5324 CHECK(tostr(tree) == ""
5325 "'block map':" "\n"
5326 " 'block key': \"block val\"" "\n"
5327 "'block seq': [\"block val 1\",\"block val 2\",\"quoted\"]" "\n"
5328 "'flow map, singleline':" "\n"
5329 " 'flow key': \"flow val\"" "\n"
5330 "'flow seq, singleline':" "\n"
5331 " - \"flow val\"" "\n"
5332 " - \"flow val\"" "\n"
5333 "'flow map, multiline':" "\n"
5334 " 'flow key': \"flow val\"" "\n"
5335 "'flow seq, multiline': [\"flow val\",\"flow val\"]" "\n"
5336 "");
5337 /// see also:
5338 /// - @ref ryml::scalar_style_choose_block()
5339 /// - @ref ryml::scalar_style_choose_flow()
5340 /// - @ref ryml::scalar_style_choose_json()
5341 /// - @ref ryml::scalar_style_query_squo()
5342 /// - @ref ryml::scalar_style_query_plain_flow()
5343 /// - @ref ryml::scalar_style_query_plain_block()
5344}
5345
5346
5347//-----------------------------------------------------------------------------
5348
5349/** Shows how to control formatting of flow styles. */
5351{
5352 // we will be using this helper throughout this function
5353 auto tostr = [](ryml::ConstNodeRef n, ryml::EmitOptions opts) {
5354 return ryml::emitrs_yaml<std::string>(n, opts);
5355 };
5356 auto tostr_json = [](ryml::ConstNodeRef n, ryml::EmitOptions opts) {
5357 return ryml::emitrs_json<std::string>(n, opts);
5358 };
5359 const ryml::EmitOptions emit_defaults = ryml::EmitOptions{};
5360 // let's parse this, which is in FLOW_ML1 (flow multiline, 1 value per line):
5361 ryml::csubstr yaml = ""
5362 "{" "\n"
5363 " map: {" "\n"
5364 " seq: [" "\n"
5365 " 0," "\n"
5366 " 1," "\n"
5367 " 2," "\n"
5368 " 3," "\n"
5369 " [40,41]" "\n"
5370 " ]" "\n"
5371 " }" "\n"
5372 "}" "\n"
5373 "";
5374 // note that the parser defaults to detecting multiline flow
5375 // (FLOW_ML1) containers:
5376 {
5377 const ryml::Tree tree = ryml::parse_in_arena(yaml);
5378 CHECK(tree["map"].is_flow_ml1()); // etc
5379 CHECK(tree["map"]["seq"].is_flow_ml1()); // etc
5380 CHECK(tree["map"]["seq"][4].is_flow_sl()); // etc
5381 // emitted yaml is exactly equal to parsed yaml:
5382 CHECK(tostr(tree, emit_defaults) == yaml);
5383 // json looks similar (except for the double quotes):
5384 CHECK(tostr_json(tree, emit_defaults) ==
5385 "{" "\n"
5386 " \"map\": {" "\n"
5387 " \"seq\": [" "\n"
5388 " 0," "\n"
5389 " 1," "\n"
5390 " 2," "\n"
5391 " 3," "\n"
5392 " [40,41]" "\n"
5393 " ]" "\n"
5394 " }" "\n"
5395 "}" "\n"
5396 "");
5397 }
5398 // if you prefer to shorten the emitted yaml, you can set the
5399 // parser to disable flow multiline detection. It will then pick
5400 // singleline flow (FLOW_SL) for all flow containers:
5401 {
5403 .detect_flow_ml(false);
5404 const ryml::Tree tree = ryml::parse_in_arena(yaml, opts);
5405 CHECK(tree["map"].is_flow_sl()); // etc
5406 // notice how this is smaller now:
5407 CHECK(tostr(tree, emit_defaults) ==
5408 "{map: {seq: [0,1,2,3,[40,41]]}}");
5409 // and json as well
5410 CHECK(tostr_json(tree, emit_defaults) ==
5411 "{\"map\": {\"seq\": [0,1,2,3,[40,41]]}}");
5412 // you can also force spaces everywhere without adding
5413 // FLOW_SPC in individual containers:
5414 const ryml::EmitOptions with_spaces = ryml::EmitOptions{}
5415 .force_flow_spc(true);
5416 CHECK(tostr(tree, with_spaces) ==
5417 "{map: {seq: [0, 1, 2, 3, [40, 41]]}}");
5418 // and json as well
5419 CHECK(tostr_json(tree, with_spaces) ==
5420 "{\"map\": {\"seq\": [0, 1, 2, 3, [40, 41]]}}");
5421 }
5422 // or you can still have the default detection of flow_ml, but set
5423 // it to pick FLOW_MLN (multiline, n values per line), instead of
5424 // the default FLOW_ML1 (multiline, 1 values per line)
5425 {
5428 const ryml::Tree tree = ryml::parse_in_arena(yaml, opts);
5429 CHECK(tree["map"].is_flow_mln());
5430 CHECK(tree["map"]["seq"][4].is_flow_sl()); // [40,41] is FLOW_SL
5431 CHECK(tostr(tree, emit_defaults) ==
5432 "{" "\n"
5433 " map: {" "\n"
5434 " seq: [" "\n"
5435 " 0,1,2,3,[40,41]" "\n"
5436 " ]" "\n"
5437 " }" "\n"
5438 "}" "\n");
5439 CHECK(tostr_json(tree, emit_defaults) ==
5440 "{" "\n"
5441 " \"map\": {" "\n"
5442 " \"seq\": [" "\n"
5443 " 0,1,2,3,[40,41]" "\n"
5444 " ]" "\n"
5445 " }" "\n"
5446 "}" "\n");
5447 // now with spaces:
5448 const ryml::EmitOptions with_spaces = ryml::EmitOptions{}
5449 .force_flow_spc(true);
5450 CHECK(tostr(tree, with_spaces) ==
5451 "{" "\n"
5452 " map: {" "\n"
5453 " seq: [" "\n"
5454 " 0, 1, 2, 3, [40, 41]" "\n"
5455 " ]" "\n"
5456 " }" "\n"
5457 "}" "\n");
5458 CHECK(tostr_json(tree, with_spaces) ==
5459 "{" "\n"
5460 " \"map\": {" "\n"
5461 " \"seq\": [" "\n"
5462 " 0, 1, 2, 3, [40, 41]" "\n"
5463 " ]" "\n"
5464 " }" "\n"
5465 "}" "\n");
5466 }
5467 // you can also disable indentation of both FLOW_ML1 and FLOW_MLN
5468 // (see more details in @ref sample_style_flow_ml_indent())
5469 {
5470 const ryml::EmitOptions noindent = ryml::EmitOptions{}
5471 .indent_flow_ml(false);
5472 const ryml::Tree tree = ryml::parse_in_arena(yaml);
5473 CHECK(tree["map"].is_flow_ml1());
5474 CHECK(tree["map"]["seq"][4].is_flow_sl()); // [40,41] is FLOW_SL
5475 CHECK(tostr(tree, noindent) == ""
5476 "{" "\n"
5477 "map: {" "\n"
5478 "seq: [" "\n"
5479 "0," "\n"
5480 "1," "\n"
5481 "2," "\n"
5482 "3," "\n"
5483 "[40,41]" "\n"
5484 "]" "\n"
5485 "}" "\n"
5486 "}" "\n"
5487 "");
5488 CHECK(tostr_json(tree, noindent) == ""
5489 "{" "\n"
5490 "\"map\": {" "\n"
5491 "\"seq\": [" "\n"
5492 "0," "\n"
5493 "1," "\n"
5494 "2," "\n"
5495 "3," "\n"
5496 "[40,41]" "\n"
5497 "]" "\n"
5498 "}" "\n"
5499 "}" "\n"
5500 "");
5501 }
5502 // finally, you can control the number of columns in FLOW_MLN:
5503 {
5504 // let's pick a different example to make this clearer
5505 ryml::csubstr yaml2 = ""
5506 "[" "\n"
5507 " 0, 1, 2, 3, 4, 5, 6, 7, 8, 9," "\n"
5508 " 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, " "\n"
5509 " 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, " "\n"
5510 " 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, " "\n"
5511 " 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, " "\n"
5512 " 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, " "\n"
5513 " 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, " "\n"
5514 " 70, 71, 72, 73, 74, 75, 76, 77, 78, 79 " "\n"
5515 "]";
5516 // Let's force the parser to pick FLOW_MLN instead of
5517 // FLOW_ML1. We're doing that because wrapping is only done in
5518 // FLOW_MLN and -- as their names imply -- FLOW_SL is
5519 // single-line, and FLOW_ML1 is 1 value per line.
5522 const ryml::Tree tree = ryml::parse_in_arena(yaml2, opts);
5523 CHECK(tree.rootref().type().is_flow_mln());
5524 // default max columns is 80:
5525 CHECK(tostr(tree, emit_defaults) == ""
5526 "[\n"
5527 " 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"
5528 " 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"
5529 " 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"
5530 "]\n"
5531 "");
5532 CHECK(tostr_json(tree, emit_defaults) == ""
5533 "[\n"
5534 " 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,\n"
5535 " 29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,\n"
5536 " 55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79\n"
5537 "]\n"
5538 "");
5539 // let's try setting max columns to 40:
5540 const ryml::EmitOptions maxcols40 = ryml::EmitOptions{}
5541 .max_cols(40);
5542 CHECK(tostr(tree, maxcols40) == ""
5543 "[\n"
5544 " 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,\n"
5545 " 16,17,18,19,20,21,22,23,24,25,26,27,28,\n"
5546 " 29,30,31,32,33,34,35,36,37,38,39,40,41,\n"
5547 " 42,43,44,45,46,47,48,49,50,51,52,53,54,\n"
5548 " 55,56,57,58,59,60,61,62,63,64,65,66,67,\n"
5549 " 68,69,70,71,72,73,74,75,76,77,78,79\n"
5550 "]\n"
5551 "");
5552 CHECK(tostr_json(tree, maxcols40) == ""
5553 "[\n"
5554 " 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,\n"
5555 " 16,17,18,19,20,21,22,23,24,25,26,27,28,\n"
5556 " 29,30,31,32,33,34,35,36,37,38,39,40,41,\n"
5557 " 42,43,44,45,46,47,48,49,50,51,52,53,54,\n"
5558 " 55,56,57,58,59,60,61,62,63,64,65,66,67,\n"
5559 " 68,69,70,71,72,73,74,75,76,77,78,79\n"
5560 "]\n"
5561 "");
5562 // Note that you can globally force spaces everywhere through
5563 // the emit options:
5564 const ryml::EmitOptions with_spaces = ryml::EmitOptions{}
5565 .force_flow_spc(true);
5566 CHECK(tostr(tree, with_spaces) == ""
5567 "[\n"
5568 " 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,\n"
5569 " 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41,\n"
5570 " 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,\n"
5571 " 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79\n"
5572 "]\n"
5573 "");
5574 CHECK(tostr_json(tree, with_spaces) == ""
5575 "[\n"
5576 " 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,\n"
5577 " 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41,\n"
5578 " 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,\n"
5579 " 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79\n"
5580 "]\n"
5581 "");
5582 // and you can combine spaces with max columns:
5583 const ryml::EmitOptions maxcols40_spc = ryml::EmitOptions{}
5584 .max_cols(40)
5585 .force_flow_spc(true);
5586 CHECK(tostr(tree, maxcols40_spc) == ""
5587 "[\n"
5588 " 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,\n"
5589 " 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,\n"
5590 " 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,\n"
5591 " 32, 33, 34, 35, 36, 37, 38, 39, 40, 41,\n"
5592 " 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,\n"
5593 " 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,\n"
5594 " 62, 63, 64, 65, 66, 67, 68, 69, 70, 71,\n"
5595 " 72, 73, 74, 75, 76, 77, 78, 79\n"
5596 "]\n"
5597 "");
5598 CHECK(tostr_json(tree, maxcols40_spc) == ""
5599 "[\n"
5600 " 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,\n"
5601 " 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,\n"
5602 " 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,\n"
5603 " 32, 33, 34, 35, 36, 37, 38, 39, 40, 41,\n"
5604 " 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,\n"
5605 " 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,\n"
5606 " 62, 63, 64, 65, 66, 67, 68, 69, 70, 71,\n"
5607 " 72, 73, 74, 75, 76, 77, 78, 79\n"
5608 "]\n"
5609 "");
5610 // and you can combine spaces with max columns with no indentation:
5611 const ryml::EmitOptions maxcols40_spc_noindent = ryml::EmitOptions{}
5612 .max_cols(40)
5613 .force_flow_spc(true)
5614 .indent_flow_ml(false);
5615 CHECK(tostr(tree, maxcols40_spc_noindent) == ""
5616 "[\n"
5617 "0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,\n"
5618 "13, 14, 15, 16, 17, 18, 19, 20, 21, 22,\n"
5619 "23, 24, 25, 26, 27, 28, 29, 30, 31, 32,\n"
5620 "33, 34, 35, 36, 37, 38, 39, 40, 41, 42,\n"
5621 "43, 44, 45, 46, 47, 48, 49, 50, 51, 52,\n"
5622 "53, 54, 55, 56, 57, 58, 59, 60, 61, 62,\n"
5623 "63, 64, 65, 66, 67, 68, 69, 70, 71, 72,\n"
5624 "73, 74, 75, 76, 77, 78, 79\n"
5625 "]\n"
5626 "");
5627 CHECK(tostr_json(tree, maxcols40_spc_noindent) == ""
5628 "[\n"
5629 "0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,\n"
5630 "13, 14, 15, 16, 17, 18, 19, 20, 21, 22,\n"
5631 "23, 24, 25, 26, 27, 28, 29, 30, 31, 32,\n"
5632 "33, 34, 35, 36, 37, 38, 39, 40, 41, 42,\n"
5633 "43, 44, 45, 46, 47, 48, 49, 50, 51, 52,\n"
5634 "53, 54, 55, 56, 57, 58, 59, 60, 61, 62,\n"
5635 "63, 64, 65, 66, 67, 68, 69, 70, 71, 72,\n"
5636 "73, 74, 75, 76, 77, 78, 79\n"
5637 "]\n"
5638 "");
5639 }
5640 // Note that FLOW_SPC is /not/ detected by the parser, and that
5641 // depending on the parse options, either FLOW_ML1 or FLOW_MLN
5642 // will be used for /all/ multiline flow containers.
5643}
5644
5645
5646//-----------------------------------------------------------------------------
5647
5648/** control the indentation of emitted flow multiline containers */
5650{
5651 // we will be using this helper throughout this function
5652 auto tostr = [](ryml::ConstNodeRef n, ryml::EmitOptions opts) {
5653 return ryml::emitrs_yaml<std::string>(n, opts);
5654 };
5655 ryml::csubstr yaml = "{map: {seq: [0, 1, 2, 3, [40, 41]]}}";
5656 ryml::Tree tree = ryml::parse_in_arena(yaml);
5657 ryml::EmitOptions defaults = {};
5659 CHECK(tostr(tree, defaults) == "{map: {seq: [0,1,2,3,[40,41]]}}");
5660 // let's now set the style to FLOW_ML1 (it was FLOW_SL)
5663 tree["map"]["seq"].set_container_style(ryml::FLOW_ML1);
5664 tree["map"]["seq"][4].set_container_style(ryml::FLOW_ML1);
5665 // by default FLOW_ML1 prints one value per line, indented:
5666 CHECK(tostr(tree, defaults) ==
5667 "{" "\n"
5668 " map: {" "\n"
5669 " seq: [" "\n"
5670 " 0," "\n"
5671 " 1," "\n"
5672 " 2," "\n"
5673 " 3," "\n"
5674 " [" "\n"
5675 " 40," "\n"
5676 " 41" "\n"
5677 " ]" "\n"
5678 " ]" "\n"
5679 " }" "\n"
5680 "}" "\n"
5681 "");
5682 // if we use the noindent options, then each value is put at the
5683 // beginning of the line
5684 CHECK(tostr(tree, noindent) ==
5685 "{" "\n"
5686 "map: {" "\n"
5687 "seq: [" "\n"
5688 "0," "\n"
5689 "1," "\n"
5690 "2," "\n"
5691 "3," "\n"
5692 "[" "\n"
5693 "40," "\n"
5694 "41" "\n"
5695 "]" "\n"
5696 "]" "\n"
5697 "}" "\n"
5698 "}" "\n"
5699 "");
5700 // Note that the noindent option will safely respect any prior
5701 // indent level from enclosing block containers! For example:
5703 CHECK(tostr(tree, noindent) == ""// notice it is indented at the map level
5704 "map: {" "\n"
5705 " seq: [" "\n"
5706 " 0," "\n"
5707 " 1," "\n"
5708 " 2," "\n"
5709 " 3," "\n"
5710 " [" "\n"
5711 " 40," "\n"
5712 " 41" "\n"
5713 " ]" "\n"
5714 " ]" "\n"
5715 " }" "\n"
5716 "");
5717 // Let's set it one BLOCK level further:
5718 tree["map"].set_container_style(ryml::BLOCK);
5719 CHECK(tostr(tree, noindent) == // notice it is indented one more level
5720 "map:" "\n"
5721 " seq: [" "\n"
5722 " 0," "\n"
5723 " 1," "\n"
5724 " 2," "\n"
5725 " 3," "\n"
5726 " [" "\n"
5727 " 40," "\n"
5728 " 41" "\n"
5729 " ]" "\n"
5730 " ]" "\n"
5731 "");
5732}
5733
5734
5735//-----------------------------------------------------------------------------
5736
5737/** shows how to parse and emit JSON.
5738 *
5739 * To emit YAML parsed from JSON, see also @ref sample_style() for
5740 * info on clearing the style flags (example below). */
5742{
5743 ryml::csubstr json = ""
5744 "{" "\n"
5745 " \"doe\": \"a deer, a female deer\"," "\n"
5746 " \"ray\": \"a drop of golden sun\"," "\n"
5747 " \"me\": \"a name, I call myself\"," "\n"
5748 " \"far\": \"a long long way to go\"" "\n"
5749 "}" "\n"
5750 "";
5751 // Since JSON is a subset of YAML, parsing JSON is just the
5752 // same as YAML:
5753 ryml::Tree tree = ryml::parse_in_arena(json);
5754 // If you are sure the source is valid json, you can use the
5755 // appropriate parse_json overload, which is stricter and faster
5756 // because json has a much smaller grammar:
5757 ryml::Tree json_tree = ryml::parse_json_in_arena(json);
5758 // to emit JSON:
5760 CHECK(ryml::emitrs_json<std::string>(json_tree) == json);
5761 // to emit JSON to a stream:
5762 std::stringstream ss;
5763 ss << ryml::as_json(tree); // <- mark it like this
5764 CHECK(ss.str() == json);
5765 // Note the following limitations on the json emitter:
5766 //
5767 // - YAML streams are emitted as seqs by default. If you want to
5768 // flag this, and want it to be an error, there is a setting to
5769 // control this in EmitOptions.
5770 //
5771 // - YAML tags cannot be emitted as JSON, and are allowed only if the
5772 // relevant setting is enabled in EmitOptions.
5773 //
5774 // - Likewise, anchors and references cannot be emitted as JSON
5775 // and are allowed only if the relevant setting is enabled in
5776 // EmitOptions.
5777 //
5778
5779 // Note that when parsing JSON, ryml will set the style of each
5780 // node in the JSON. This means that if you emit YAML from a tree
5781 // parsed from JSON, it will look mostly the same as the original
5782 // JSON:
5783 CHECK(ryml::emitrs_yaml<std::string>(json_tree) == json);
5784 // If you want to avoid this, you will need to clear the style.
5785 json_tree.rootref().clear_style(); // clear the style of the map (without recursing)
5786 // note that this is now block mode. That is because when no
5787 // style is set, ryml defaults to emitting in block mode.
5788 CHECK(ryml::emitrs_yaml<std::string>(json_tree) == ""
5789 "\"doe\": \"a deer, a female deer\"" "\n"
5790 "\"ray\": \"a drop of golden sun\"" "\n"
5791 "\"me\": \"a name, I call myself\"" "\n"
5792 "\"far\": \"a long long way to go\"" "\n"
5793 "");
5794 // if you don't want the double quotes in the scalar, you can
5795 // recurse:
5796 json_tree.rootref().clear_style(/*recurse*/true);
5797 // so now when emitting you will get this:
5798 CHECK(ryml::emitrs_yaml<std::string>(json_tree) == ""
5799 "doe: a deer, a female deer" "\n"
5800 "ray: a drop of golden sun" "\n"
5801 "me: a name, I call myself" "\n"
5802 "far: a long long way to go" "\n"
5803 "");
5804 // see in particular sample_style() for more examples
5805}
5806
5807
5808//-----------------------------------------------------------------------------
5809
5810/** demonstrates usage with anchors and alias references.
5811
5812Note that dereferencing is opt-in; after parsing, you have to call
5813@ref c4::yml::Tree::resolve() explicitly if you want resolved
5814references in the tree. This method will resolve all references and
5815substitute the anchored values in place of the reference.
5816
5817The @ref c4::yml::Tree::resolve() method first does a full traversal
5818of the tree to gather all anchors and references in a separate
5819collection, then it goes through that collection to locate the names,
5820which it does by obeying the YAML standard diktat that
5821
5822 > an alias node refers to the most recent node in
5823 > the serialization having the specified anchor
5824
5825So, depending on the number of anchor/alias nodes, this is a
5826potentially expensive operation, with a best-case linear complexity
5827(from the initial traversal) and a worst-case quadratic complexity (if
5828every node has an alias/anchor). This potential cost is the reason for
5829requiring an explicit call to @ref c4::yml::Tree::resolve() */
5831{
5832 std::string unresolved = ""
5833 "base: &base" "\n"
5834 " name: Everyone has same name" "\n"
5835 "foo: &foo" "\n"
5836 " <<: *base" "\n"
5837 " age: 10" "\n"
5838 "bar: &bar" "\n"
5839 " <<: *base" "\n"
5840 " age: 20" "\n"
5841 "bill_to: &id001" "\n"
5842 " street: |-" "\n"
5843 " 123 Tornado Alley" "\n"
5844 " Suite 16" "\n"
5845 " city: East Centerville" "\n"
5846 " state: KS" "\n"
5847 "ship_to: *id001" "\n"
5848 "&keyref key: &valref val" "\n"
5849 "*valref : *keyref" "\n"
5850 "";
5851 std::string resolved = ""
5852 "base:" "\n"
5853 " name: Everyone has same name" "\n"
5854 "foo:" "\n"
5855 " name: Everyone has same name" "\n"
5856 " age: 10" "\n"
5857 "bar:" "\n"
5858 " name: Everyone has same name" "\n"
5859 " age: 20" "\n"
5860 "bill_to:" "\n"
5861 " street: |-" "\n"
5862 " 123 Tornado Alley" "\n"
5863 " Suite 16" "\n"
5864 " city: East Centerville" "\n"
5865 " state: KS" "\n"
5866 "ship_to:" "\n"
5867 " street: |-" "\n"
5868 " 123 Tornado Alley" "\n"
5869 " Suite 16" "\n"
5870 " city: East Centerville" "\n"
5871 " state: KS" "\n"
5872 "key: val" "\n"
5873 "val: key" "\n"
5874 "";
5875
5877 // by default, references are not resolved when parsing:
5878 CHECK( ! tree["base"].has_key_anchor());
5879 CHECK( tree["base"].has_val_anchor());
5880 CHECK( tree["base"].val_anchor() == "base");
5881 CHECK( tree["key"].key_anchor() == "keyref");
5882 CHECK( tree["key"].val_anchor() == "valref");
5883 CHECK( tree["*valref"].is_key_ref());
5884 CHECK( tree["*valref"].is_val_ref());
5885 CHECK( tree["*valref"].key_ref() == "valref");
5886 CHECK( tree["*valref"].val_ref() == "keyref");
5887
5888 // to resolve references, simply call tree.resolve(),
5889 // which will perform the reference instantiations:
5890 tree.resolve();
5891
5892 // all the anchors and references are substistuted and then removed:
5893 CHECK( ! tree["base"].has_key_anchor());
5894 CHECK( ! tree["base"].has_val_anchor());
5895 CHECK( ! tree["base"].has_val_anchor());
5896 CHECK( ! tree["key"].has_key_anchor());
5897 CHECK( ! tree["key"].has_val_anchor());
5898 CHECK( ! tree["val"].is_key_ref()); // notice *valref is now turned to val
5899 CHECK( ! tree["val"].is_val_ref()); // notice *valref is now turned to val
5900
5901 CHECK(tree["ship_to"]["city"].val() == "East Centerville");
5902 CHECK(tree["ship_to"]["state"].val() == "KS");
5903}
5904
5905/** demonstrates how to use the API to programatically create anchors
5906 * and aliases */
5908{
5909 // part 1: anchor/ref
5910 {
5911 ryml::Tree t;
5913 t["kanchor"].set_val("2");
5914 t["kanchor"].set_key_anchor("kanchor");
5915 t["vanchor"].set_val("3");
5916 t["vanchor"].set_val_anchor("vanchor");
5917 // to set a reference, need to call .set_val_ref()/.set_key_ref()
5918 t["kref"].set_val_ref("kanchor");
5919 t["vref"].set_val_ref("vanchor");
5920 t["nref"].set_val("*vanchor"); // NOTE: this is not set as a reference in the tree!
5922 "&kanchor kanchor: 2" "\n"
5923 "vanchor: &vanchor 3" "\n"
5924 "kref: *kanchor" "\n"
5925 "vref: *vanchor" "\n"
5926 // note that ryml emits nref with quotes to disambiguate
5927 // (because no style was set)
5928 "nref: '*vanchor'" "\n"
5929 "");
5930 t.resolve();
5932 "kanchor: 2" "\n"
5933 "vanchor: 3" "\n"
5934 "kref: kanchor" "\n"
5935 "vref: 3" "\n"
5936 // note that nref was not resolved
5937 "nref: '*vanchor'" "\n"
5938 "");
5939 }
5940
5941 // part 2: simple inheritance (ie, adding `<<: *anchor` nodes)
5942 {
5944 "orig: &orig {foo: bar, baz: bat}" "\n"
5945 "copy: {}" "\n"
5946 "notcopy: {}" "\n"
5947 "notref: {}" "\n"
5948 "");
5949 t["copy"]["<<"].set_val_ref("orig");
5950 t["notcopy"]["test"].set_val_ref("orig");
5951 t["notcopy"]["<<"].set_val_ref("orig");
5952 t["notref"]["<<"].set_val("*orig"); // not a reference! .set_val_ref() was not called
5954 "orig: &orig {foo: bar,baz: bat}" "\n"
5955 "copy: {<<: *orig}" "\n"
5956 "notcopy: {test: *orig,<<: *orig}" "\n"
5957 "notref: {<<: '*orig'}" "\n"
5958 "");
5959 t.resolve();
5961 "orig: {foo: bar,baz: bat}" "\n"
5962 "copy: {foo: bar,baz: bat}" "\n"
5963 "notcopy: {test: {foo: bar,baz: bat},foo: bar,baz: bat}" "\n"
5964 "notref: {<<: '*orig'}" "\n"
5965 "");
5966 }
5967
5968 // part 3: multiple inheritance (ie, `<<: [*ref1,*ref2,*etc]`)
5969 {
5971 "orig1: &orig1 {foo: bar}" "\n"
5972 "orig2: &orig2 {baz: bat}" "\n"
5973 "orig3: &orig3 {and: more}" "\n"
5974 "copy: {}" "\n"
5975 "");
5976 ryml::NodeRef seq = t["copy"]["<<"];
5977 seq.set_seq();
5978 seq.append_child().set_val_ref("orig1");
5979 seq.append_child().set_val_ref("orig2");
5980 seq.append_child().set_val_ref("orig3");
5982 "orig1: &orig1 {foo: bar}" "\n"
5983 "orig2: &orig2 {baz: bat}" "\n"
5984 "orig3: &orig3 {and: more}" "\n"
5985 "copy: {<<: [*orig1,*orig2,*orig3]}" "\n"
5986 "");
5987 t.resolve();
5989 "orig1: {foo: bar}" "\n"
5990 "orig2: {baz: bat}" "\n"
5991 "orig3: {and: more}" "\n"
5992 "copy: {foo: bar,baz: bat,and: more}" "\n");
5993 }
5994}
5995
5996
5997//-----------------------------------------------------------------------------
5998
6000{
6001 const std::string yaml = ""
6002 "--- !!map" "\n"
6003 "a: 0" "\n"
6004 "b: 1" "\n"
6005 "--- !map" "\n"
6006 "a: b" "\n"
6007 "--- !!seq" "\n"
6008 "- a" "\n"
6009 "- b" "\n"
6010 "--- !!str a b" "\n"
6011 "--- !!str 'a: b'" "\n"
6012 "---" "\n"
6013 "!!str a: b" "\n"
6014 "--- !!set" "\n"
6015 "? a" "\n"
6016 "? b" "\n"
6017 "--- !!set" "\n"
6018 "a:" "\n"
6019 "--- !!seq" "\n"
6020 "- !!int 0" "\n"
6021 "- !!str 1" "\n"
6022 "";
6024 const ryml::ConstNodeRef root = tree.rootref();
6025 CHECK(root.is_stream());
6026 CHECK(root.num_children() == 9);
6027 for(ryml::ConstNodeRef doc : root.children())
6028 CHECK(doc.is_doc());
6029 // tags are kept verbatim from the source:
6030 CHECK(root[0].has_val_tag());
6031 CHECK(root[0].val_tag() == "!!map"); // valid only if the node has a val tag
6032 CHECK(root[1].val_tag() == "!map");
6033 CHECK(root[2].val_tag() == "!!seq");
6034 CHECK(root[3].val_tag() == "!!str");
6035 CHECK(root[4].val_tag() == "!!str");
6036 CHECK(root[5]["a"].has_key_tag());
6037 CHECK(root[5]["a"].key_tag() == "!!str"); // valid only if the node has a key tag
6038 CHECK(root[6].val_tag() == "!!set");
6039 CHECK(root[7].val_tag() == "!!set");
6040 CHECK(root[8].val_tag() == "!!seq");
6041 CHECK(root[8][0].val_tag() == "!!int");
6042 CHECK(root[8][1].val_tag() == "!!str");
6043 // ryml also provides a complete toolbox to deal with tags.
6044 // there is an enumeration for the standard YAML tags:
6045 CHECK(ryml::to_tag("!map") == ryml::TAG_NONE);
6046 CHECK(ryml::to_tag("!!map") == ryml::TAG_MAP);
6047 CHECK(ryml::to_tag("!!seq") == ryml::TAG_SEQ);
6048 CHECK(ryml::to_tag("!!str") == ryml::TAG_STR);
6049 CHECK(ryml::to_tag("!!int") == ryml::TAG_INT);
6050 CHECK(ryml::to_tag("!!set") == ryml::TAG_SET);
6051 // given a tag enum, you can fetch the short tag string:
6053 CHECK(ryml::from_tag(ryml::TAG_MAP) == "!!map");
6054 CHECK(ryml::from_tag(ryml::TAG_SEQ) == "!!seq");
6055 CHECK(ryml::from_tag(ryml::TAG_STR) == "!!str");
6056 CHECK(ryml::from_tag(ryml::TAG_INT) == "!!int");
6057 CHECK(ryml::from_tag(ryml::TAG_SET) == "!!set");
6058 // you can also fetch the long tag string:
6060 CHECK(ryml::from_tag_long(ryml::TAG_MAP) == "<tag:yaml.org,2002:map>");
6061 CHECK(ryml::from_tag_long(ryml::TAG_SEQ) == "<tag:yaml.org,2002:seq>");
6062 CHECK(ryml::from_tag_long(ryml::TAG_STR) == "<tag:yaml.org,2002:str>");
6063 CHECK(ryml::from_tag_long(ryml::TAG_INT) == "<tag:yaml.org,2002:int>");
6064 CHECK(ryml::from_tag_long(ryml::TAG_SET) == "<tag:yaml.org,2002:set>");
6065 // and likewise:
6066 CHECK(ryml::to_tag("!map") == ryml::TAG_NONE);
6067 CHECK(ryml::to_tag("<tag:yaml.org,2002:map>") == ryml::TAG_MAP);
6068 CHECK(ryml::to_tag("<tag:yaml.org,2002:seq>") == ryml::TAG_SEQ);
6069 CHECK(ryml::to_tag("<tag:yaml.org,2002:str>") == ryml::TAG_STR);
6070 CHECK(ryml::to_tag("<tag:yaml.org,2002:int>") == ryml::TAG_INT);
6071 CHECK(ryml::to_tag("<tag:yaml.org,2002:set>") == ryml::TAG_SET);
6072 // to normalize a tag as much as possible, use normalize_tag():
6073 CHECK(ryml::normalize_tag("!!map") == "!!map");
6074 CHECK(ryml::normalize_tag("!<tag:yaml.org,2002:map>") == "!!map");
6075 CHECK(ryml::normalize_tag("<tag:yaml.org,2002:map>") == "!!map");
6076 CHECK(ryml::normalize_tag("tag:yaml.org,2002:map") == "!!map");
6077 CHECK(ryml::normalize_tag("!<!!map>") == "<!!map>");
6078 CHECK(ryml::normalize_tag("!map") == "!map");
6079 CHECK(ryml::normalize_tag("!my!foo") == "!my!foo");
6080 // and also for the long form:
6081 CHECK(ryml::normalize_tag_long("!!map") == "<tag:yaml.org,2002:map>");
6082 CHECK(ryml::normalize_tag_long("!<tag:yaml.org,2002:map>") == "<tag:yaml.org,2002:map>");
6083 CHECK(ryml::normalize_tag_long("<tag:yaml.org,2002:map>") == "<tag:yaml.org,2002:map>");
6084 CHECK(ryml::normalize_tag_long("tag:yaml.org,2002:map") == "<tag:yaml.org,2002:map>");
6085 CHECK(ryml::normalize_tag_long("!<!!map>") == "<!!map>");
6086 CHECK(ryml::normalize_tag_long("!map") == "!map");
6087 // The tree provides the following methods applying to every node
6088 // with a key and/or val tag:
6089 ryml::Tree normalized_tree = tree;
6090 normalized_tree.normalize_tags(); // normalize all tags in short form
6091 CHECK(ryml::emitrs_yaml<std::string>(normalized_tree) == ""
6092 "--- !!map" "\n"
6093 "a: 0" "\n"
6094 "b: 1" "\n"
6095 "--- !map" "\n"
6096 "a: b" "\n"
6097 "--- !!seq" "\n"
6098 "- a" "\n"
6099 "- b" "\n"
6100 "--- !!str a b" "\n"
6101 "--- !!str 'a: b'" "\n"
6102 "---" "\n"
6103 "!!str a: b" "\n"
6104 "--- !!set" "\n"
6105 "a: " "\n"
6106 "b: " "\n"
6107 "--- !!set" "\n"
6108 "a: " "\n"
6109 "--- !!seq" "\n"
6110 "- !!int 0" "\n"
6111 "- !!str 1" "\n"
6112 "");
6113 ryml::Tree normalized_tree_long = tree;
6114 normalized_tree_long.normalize_tags_long(); // normalize all tags in short form
6115 CHECK(ryml::emitrs_yaml<std::string>(normalized_tree_long) == ""
6116 "--- !<tag:yaml.org,2002:map>" "\n"
6117 "a: 0" "\n"
6118 "b: 1" "\n"
6119 "--- !map" "\n"
6120 "a: b" "\n"
6121 "--- !<tag:yaml.org,2002:seq>" "\n"
6122 "- a" "\n"
6123 "- b" "\n"
6124 "--- !<tag:yaml.org,2002:str> a b" "\n"
6125 "--- !<tag:yaml.org,2002:str> 'a: b'" "\n"
6126 "---" "\n"
6127 "!<tag:yaml.org,2002:str> a: b" "\n"
6128 "--- !<tag:yaml.org,2002:set>" "\n"
6129 "a: " "\n"
6130 "b: " "\n"
6131 "--- !<tag:yaml.org,2002:set>" "\n"
6132 "a: " "\n"
6133 "--- !<tag:yaml.org,2002:seq>" "\n"
6134 "- !<tag:yaml.org,2002:int> 0" "\n"
6135 "- !<tag:yaml.org,2002:str> 1" "\n"
6136 "");
6137}
6138
6139
6140//-----------------------------------------------------------------------------
6141
6143{
6144 const std::string yaml = ""
6145 "%TAG !m! !my-" "\n"
6146 "--- # Bulb here" "\n"
6147 "!m!light fluorescent" "\n"
6148 "..." "\n"
6149 "%TAG !m! !meta-" "\n"
6150 "--- # Color here" "\n"
6151 "!m!light green" "\n"
6152 "";
6153 // tags are not resolved by default:
6156 "%TAG !m! !my-" "\n"
6157 "--- !m!light fluorescent" "\n"
6158 "..." "\n"
6159 "%TAG !m! !meta-" "\n"
6160 "--- !m!light green" "\n"
6161 "");
6162 // Use Tree::resolve_tags() to accomplish this in an existing
6163 // tree:
6164 ryml::TagCache tag_cache; // reduces memory requirements by reusing resolved tags
6165 tree.resolve_tags(tag_cache);
6167 "%TAG !m! !my-" "\n"
6168 "--- !<!my-light> fluorescent" "\n"
6169 "..." "\n"
6170 "%TAG !m! !meta-" "\n"
6171 "--- !<!meta-light> green" "\n"
6172 "");
6173 // You can also Use ParserOptions to force resolution of tags
6174 // while parsing:
6176 ryml::Tree resolved_tree = ryml::parse_in_arena(ryml::to_csubstr(yaml), opts);
6177 CHECK(ryml::emitrs_yaml<std::string>(resolved_tree) == ""
6178 "%TAG !m! !my-" "\n"
6179 "--- !<!my-light> fluorescent" "\n"
6180 "..." "\n"
6181 "%TAG !m! !meta-" "\n"
6182 "--- !<!meta-light> green" "\n"
6183 "");
6184 // see also tree.normalize_tags()
6185 // see also tree.normalize_tags_long()
6186}
6187
6188
6189//-----------------------------------------------------------------------------
6190
6192{
6193 ryml::csubstr yml = ""
6194 "---" "\n"
6195 "a: 0" "\n"
6196 "b: 1" "\n"
6197 "---" "\n"
6198 "c: 2" "\n"
6199 "d: 3" "\n"
6200 "---" "\n"
6201 "- 4" "\n"
6202 "- 5" "\n"
6203 "- 6" "\n"
6204 "- 7" "\n"
6205 "";
6208
6209 // iteration through docs
6210 {
6211 // using the node API
6212 const ryml::ConstNodeRef stream = tree.rootref();
6213 CHECK(stream.is_root());
6214 CHECK(stream.is_stream());
6215 CHECK(!stream.is_doc());
6216 CHECK(stream.num_children() == 3);
6217 for(const ryml::ConstNodeRef doc : stream.children())
6218 CHECK(doc.is_doc());
6219 CHECK(tree.docref(0).id() == stream.child(0).id());
6220 CHECK(tree.docref(1).id() == stream.child(1).id());
6221 CHECK(tree.docref(2).id() == stream.child(2).id());
6222 // equivalent: using the lower level index API
6223 const ryml::id_type stream_id = tree.root_id();
6224 CHECK(tree.is_root(stream_id));
6225 CHECK(tree.is_stream(stream_id));
6226 CHECK(!tree.is_doc(stream_id));
6227 CHECK(tree.num_children(stream_id) == 3);
6228 for(ryml::id_type doc_id = tree.first_child(stream_id);
6229 doc_id != ryml::NONE;
6230 doc_id = tree.next_sibling(stream_id))
6231 CHECK(tree.is_doc(doc_id));
6232 CHECK(tree.doc(0) == tree.child(stream_id, 0));
6233 CHECK(tree.doc(1) == tree.child(stream_id, 1));
6234 CHECK(tree.doc(2) == tree.child(stream_id, 2));
6235
6236 // using the node API
6237 CHECK(stream[0].is_doc());
6238 CHECK(stream[0].is_map());
6239 CHECK(stream[0]["a"].val() == "0");
6240 CHECK(stream[0]["b"].val() == "1");
6241 // equivalent: using the index API
6242 const ryml::id_type doc0_id = tree.first_child(stream_id);
6243 CHECK(tree.is_doc(doc0_id));
6244 CHECK(tree.is_map(doc0_id));
6245 CHECK(tree.val(tree.find_child(doc0_id, "a")) == "0");
6246 CHECK(tree.val(tree.find_child(doc0_id, "b")) == "1");
6247
6248 // using the node API
6249 CHECK(stream[1].is_doc());
6250 CHECK(stream[1].is_map());
6251 CHECK(stream[1]["c"].val() == "2");
6252 CHECK(stream[1]["d"].val() == "3");
6253 // equivalent: using the index API
6254 const ryml::id_type doc1_id = tree.next_sibling(doc0_id);
6255 CHECK(tree.is_doc(doc1_id));
6256 CHECK(tree.is_map(doc1_id));
6257 CHECK(tree.val(tree.find_child(doc1_id, "c")) == "2");
6258 CHECK(tree.val(tree.find_child(doc1_id, "d")) == "3");
6259
6260 // using the node API
6261 CHECK(stream[2].is_doc());
6262 CHECK(stream[2].is_seq());
6263 CHECK(stream[2][0].val() == "4");
6264 CHECK(stream[2][1].val() == "5");
6265 CHECK(stream[2][2].val() == "6");
6266 CHECK(stream[2][3].val() == "7");
6267 // equivalent: using the index API
6268 const ryml::id_type doc2_id = tree.next_sibling(doc1_id);
6269 CHECK(tree.is_doc(doc2_id));
6270 CHECK(tree.is_seq(doc2_id));
6271 CHECK(tree.val(tree.child(doc2_id, 0)) == "4");
6272 CHECK(tree.val(tree.child(doc2_id, 1)) == "5");
6273 CHECK(tree.val(tree.child(doc2_id, 2)) == "6");
6274 CHECK(tree.val(tree.child(doc2_id, 3)) == "7");
6275 }
6276
6277 // Note: ryml emits streams as a JSON seq by default:
6279 "[" "\n"
6280 " {" "\n"
6281 " \"a\": 0," "\n"
6282 " \"b\": 1" "\n"
6283 " }," "\n"
6284 " {" "\n"
6285 " \"c\": 2," "\n"
6286 " \"d\": 3" "\n"
6287 " }," "\n"
6288 " [" "\n"
6289 " 4," "\n"
6290 " 5," "\n"
6291 " 6," "\n"
6292 " 7" "\n"
6293 " ]" "\n"
6294 "]\n"
6295 "");
6296 // ... but you can use EmitOptions{} to set the emitter to fail if
6297 // it finds a stream:
6298 {
6299 ScopedErrorHandlerExample errh; // calls ryml::set_callbacks()
6301 CHECK(err_tree.callbacks() == errh.callbacks());
6302 auto err_opts = ryml::EmitOptions{}.json_err_on_stream(true);
6303 CHECK(errh.check_error_occurs([&]{
6304 return ryml::emitrs_json<std::string>(err_tree, err_opts);
6305 }));
6306 // in which case, you can avoid the error by emitting the
6307 // documents one-by-one:
6308 const std::string expected_json[] = {
6309 "{" "\n"
6310 " \"a\": 0," "\n"
6311 " \"b\": 1" "\n"
6312 "}" "\n"
6313 ,
6314 "{" "\n"
6315 " \"c\": 2," "\n"
6316 " \"d\": 3" "\n"
6317 "}" "\n"
6318 ,
6319 "[" "\n"
6320 " 4," "\n"
6321 " 5," "\n"
6322 " 6," "\n"
6323 " 7" "\n"
6324 "]" "\n"
6325 };
6326 ryml::id_type count = 0;
6327 const ryml::ConstNodeRef stream = err_tree;
6328 CHECK(stream.num_children() == (ryml::id_type)C4_COUNTOF(expected_json));
6329 for(ryml::ConstNodeRef doc : stream.children())
6330 CHECK(ryml::emitrs_json<std::string>(doc, err_opts) == expected_json[count++]);
6331 }
6332}
6333
6334
6335//-----------------------------------------------------------------------------
6336
6337// To avoid imposing a particular type of error handling, ryml uses
6338// error handler callbacks. This enables users to use exceptions, or
6339// setjmp()/longjmp(), or plain calls to abort(), as they see fit.
6340//
6341// However, it is important to note that the error callbacks must never
6342// return to the caller! Otherwise, an infinite loop or program crash
6343// will likely occur.
6344//
6345// For this reason, to recover from an error when exceptions are disabled,
6346// then a non-local jump must be performed using setjmp()/longjmp().
6347// The code below demonstrates both flows.
6348//
6349// ryml provides default error handlers, which call
6350// std::abort(). You can use the cmake option and the macro
6351// RYML_DEFAULT_CALLBACK_USES_EXCEPTIONS to have the default error
6352// handler throw an exception instead.
6353
6354/** demonstrates how to set a custom error handler for ryml */
6356{
6357 ErrorHandlerExample errh; // browse the implementation of this
6358 // class to understand more details
6359 errh.check_disabled();
6360 // set the global error handlers. Note the error callbacks must
6361 // never return: they must either throw an exception, use setjmp()
6362 // and longjmp(), or abort. Otherwise, the parser will enter into
6363 // an infinite loop, or the program may crash.
6365 errh.check_enabled();
6366 CHECK(errh.check_error_occurs([&]{
6367 ryml::Tree tree = ryml::parse_in_arena("errorhandler.yml", "[a: b\n}");
6368 }));
6369 ryml::set_callbacks(errh.original_callbacks); // restore defaults.
6370 errh.check_disabled();
6371}
6372
6373
6374//-----------------------------------------------------------------------------
6375
6377{
6378 auto cause_basic_error = []{
6379 ryml::Tree t;
6380 ryml::csubstr tag_handle = {}, tag_prefix = {}; // invalid, not filled
6381 t.add_tag_directive(tag_handle, tag_prefix, 0);
6382 };
6383 {
6384 ScopedErrorHandlerExample errh; // set the example callbacks (scoped)
6385 CHECK(errh.check_error_occurs(cause_basic_error));
6386 }
6387#ifdef RYML_WITH_EXCEPTIONS_
6388 bool gotit = false;
6389 try
6390 {
6391 cause_basic_error();
6392 }
6393 catch(ryml::ExceptionBasic const& exc)
6394 {
6395 gotit = true;
6396 ryml::csubstr msg = ryml::to_csubstr(exc.what());
6398 CHECK(!msg.empty());
6399 }
6400 CHECK(gotit);
6401#endif
6402}
6403
6404
6406{
6407 ryml::csubstr ymlsrc = ""
6408 "{" "\n"
6409 " a: b" "\n"
6410 " [" "\n"
6411 "";
6412 ryml::csubstr ymlfile = "file.yml";
6413 auto cause_parse_error = [&]{
6414 return ryml::parse_in_arena(ymlfile, ymlsrc);
6415 };
6416 // the YAML in ymlsrc must cause a parse error while it is being
6417 // parsed. We use our error handler to catch that error, and save
6418 // the error info:
6420 {
6422 CHECK(errh.check_error_occurs(cause_parse_error));
6423 // the handler in errh saves the error info in itself. Let's
6424 // use that to see the messages we get.
6425 //
6426 // this message is the short message passed into the parse
6427 // error handler:
6428 CHECK(errh.saved_msg_short == "invalid character: '['");
6429 // this message was created inside the handler, by calling
6430 // ryml::err_parse_format():
6431 CHECK(ryml::to_csubstr(errh.saved_msg_full).begins_with("file.yml:3: col=4 (12B): ERROR: [parse] invalid character: '['"));
6432 // If you keep the YAML source buffer around, you can also use
6433 // it to create/print a larger error message showing the
6434 // YAML source code context which causes the error:
6435 std::string msg_ctx = errh.saved_msg_full + "\n";
6437 msg_ctx.append(s.str, s.len);
6438 }, errh.saved_parse_loc, ymlsrc, "err");
6439 CHECK(ryml::to_csubstr(msg_ctx).begins_with("file.yml:3: col=4 (12B): ERROR: [parse] invalid character: '['"));
6440 CHECK(ryml::to_csubstr(msg_ctx).ends_with(
6441 "file.yml:3: col=4 (12B): err:" "\n"
6442 "err:" "\n"
6443 "err: [" "\n"
6444 "err: |" "\n"
6445 "err: (here)" "\n"
6446 "err:" "\n"
6447 "err: see region:" "\n"
6448 "err:" "\n"
6449 "err: {" "\n"
6450 "err: a: b" "\n"
6451 "err: [" "\n"
6452 "err: |" "\n"
6453 "err: (here)" "\n"
6454 ""));
6455 //
6456 // Let's now check the location (see the message above):
6457 CHECK(errh.saved_parse_loc.name == ymlfile);
6458 CHECK(errh.saved_parse_loc.line == 3);
6459 CHECK(errh.saved_parse_loc.col == 4);
6460 CHECK(errh.saved_parse_loc.offset == 12);
6461 CHECK(errh.saved_parse_loc.offset <= ymlsrc.len);
6462 // ... and this is the location in the ryml source code file where
6463 // this error was found:
6465 CHECK(errh.saved_basic_loc.line > 0);
6466 CHECK(errh.saved_basic_loc.col > 0);
6467 CHECK(errh.saved_basic_loc.offset > 0);
6469 }
6470 // A parse error is also a basic error. If no parse error handler
6471 // is set, then ryml falls back to a basic error:
6472 {
6473 ryml::Callbacks cb = errh.callbacks();
6474 cb.m_error_parse = nullptr;
6476 CHECK(ryml::get_callbacks().m_error_parse == nullptr);
6477 CHECK(errh.check_error_occurs(cause_parse_error));
6478 // we got a basic error instead of a parse error:
6479 CHECK(errh.saved_msg_short == "invalid character: '['");
6480 // notice that the full message now displays this as a basic
6481 // error:
6482 CHECK(errh.saved_msg_full == "file.yml:3: col=4 (12B): ERROR: [basic] invalid character: '['");
6483 // the yml location is now in the location saved from the basic error
6484 CHECK(errh.saved_basic_loc.name == ymlfile);
6485 CHECK(errh.saved_basic_loc.line == 3);
6486 CHECK(errh.saved_basic_loc.col == 4);
6487 CHECK(errh.saved_basic_loc.offset == 12);
6488 CHECK(errh.saved_basic_loc.offset <= ymlsrc.len);
6494 }
6495#ifdef RYML_WITH_EXCEPTIONS_
6496 bool gotit = false;
6497 try
6498 {
6499 cause_parse_error();
6500 }
6501 catch(ryml::ExceptionParse const& exc)
6502 {
6503 gotit = true;
6504 ryml::csubstr msg = ryml::to_csubstr(exc.what());
6505 CHECK(exc.errdata_parse.ymlloc.name == ymlfile);
6506 CHECK(exc.errdata_parse.ymlloc.line == 3);
6507 CHECK(exc.errdata_parse.ymlloc.col == 4);
6508 CHECK(exc.errdata_parse.ymlloc.offset == 12);
6509 CHECK(exc.errdata_parse.ymlloc.offset <= ymlsrc.len);
6510 // the message saved in the exception is just the concrete error description:
6511 CHECK(msg == "invalid character: '['");
6512 // to print richer error messages, ryml provides helpers to
6513 // format that description into a complete error message,
6514 // containing location and source context indication:
6515 std::string full;
6516 auto dumpfn = [&full](ryml::csubstr s) { full.append(s.str, s.len); };
6517 ryml::err_parse_format(dumpfn, msg, exc.errdata_parse);
6518 full += '\n';
6519 ryml::location_format_with_context(dumpfn, exc.errdata_parse.ymlloc, ymlsrc, "err", 3);
6520 CHECK(ryml::to_csubstr(full).begins_with("file.yml:3: col=4 (12B): ERROR: [parse] invalid character: '['"));
6521 CHECK(ryml::to_csubstr(full).ends_with(
6522 "file.yml:3: col=4 (12B): err:" "\n"
6523 "err:" "\n"
6524 "err: [" "\n"
6525 "err: |" "\n"
6526 "err: (here)" "\n"
6527 "err:" "\n"
6528 "err: see region:" "\n"
6529 "err:" "\n"
6530 "err: {" "\n"
6531 "err: a: b" "\n"
6532 "err: [" "\n"
6533 "err: |" "\n"
6534 "err: (here)" "\n"
6535 ""));
6536 }
6537 CHECK(gotit);
6538 gotit = false;
6539 try
6540 {
6541 cause_parse_error();
6542 }
6543 catch(ryml::ExceptionBasic const& exc) // use references! don't slice the exception
6544 {
6545 gotit = true;
6546 ryml::csubstr msg = ryml::to_csubstr(exc.what());
6548 CHECK(!msg.empty());
6549 }
6550 CHECK(gotit);
6551#endif
6552}
6553
6554
6555/** Visit errors happen when an error is triggered while reading from
6556 * a node. */
6558{
6559 ryml::csubstr ymlfile = "file.yml";
6560 ryml::csubstr ymlsrc = "float: 123.456";
6562 {
6564 ryml::Tree tree = ryml::parse_in_arena(ymlfile, ymlsrc);
6565 CHECK(errh.check_error_occurs([&]{
6566 int intval = 0;
6567 tree["float"].load(&intval); // cannot deserialize 123.456 to int
6568 }));
6569 // the handler in errh saves the error info in itself. Let's
6570 // use that to see the messages we get.
6571 //
6572 // this message is the short message passed into the visit error
6573 CHECK(errh.saved_msg_short == "could not deserialize node");
6574 // this message was created inside the handler, by calling
6575 // ryml::err_visit_format():
6576 CHECK(ryml::csubstr::npos != ryml::to_csubstr(errh.saved_msg_full).find("ERROR: [visit] could not deserialize node"));
6577 // The location of the visit error is of the C++ source file where
6578 // the error was detected -- NOT of the YAML source file:
6579 CHECK(errh.saved_basic_loc.name != ymlfile);
6580 // However, note that the tree and node id are available:
6581 CHECK(errh.saved_visit_tree == &tree);
6582 CHECK(errh.saved_visit_id == tree["float"].id());
6583 // see sample_error_visit_location() for an example on how
6584 // to extract the location.
6585 }
6586 // visit errors also fall back to basic errors when the visit
6587 // handler is not set (similar to the behavior of ExceptionVisit):
6588 {
6589 ryml::Callbacks cb = errh.callbacks();
6590 cb.m_error_visit = nullptr;
6592 CHECK(ryml::get_callbacks().m_error_visit == nullptr);
6593 ryml::Tree tree = ryml::parse_in_arena(ymlfile, ymlsrc);
6594 CHECK(errh.check_error_occurs([&]{
6595 int intval = 0;
6596 tree["float"].load(&intval); // cannot deserialize 123.456 to int
6597 }));
6598 // we got a basic error instead of a visit error:
6599 CHECK(errh.saved_msg_short == "could not deserialize node");
6600 // notice that the full message now displays this as a basic
6601 // error:
6602 CHECK(ryml::csubstr::npos != ryml::to_csubstr(errh.saved_msg_full).find("ERROR: [basic] could not deserialize node"));
6603 // the tree and id are not set, because this was called as a basic error
6604 CHECK(errh.saved_visit_tree == nullptr);
6607 }
6608#ifdef RYML_WITH_EXCEPTIONS_
6609 // when using the default ryml callbacks (see
6610 // RYML_NO_DEFAULT_CALLBACKS), and
6611 // RYML_DEFAULT_CALLBACK_USES_EXCEPTIONS is defined, the ryml
6612 // parse handler throws an exception of type ryml::ExceptionVisit,
6613 // which is derived from ryml::ExceptionBasic.
6614 {
6615 const ryml::Tree tree = ryml::parse_in_arena(ymlfile, ymlsrc);
6616 bool gotit = false;
6617 try
6618 {
6619 int intval = 0;
6620 tree["float"].load(&intval); // cannot deserialize 123.456 to int
6621 }
6622 catch(ryml::ExceptionVisit const& exc)
6623 {
6624 gotit = true;
6625 ryml::csubstr msg = ryml::to_csubstr(exc.what());
6627 CHECK(exc.errdata_visit.tree == &tree);
6628 CHECK(exc.errdata_visit.node == tree["float"].id());
6629 CHECK(!msg.empty());
6630 }
6631 CHECK(gotit);
6632 }
6633 // you can also catch the exception as its base,
6634 // ryml::ExceptionBasic:
6635 {
6636 const ryml::Tree tree = ryml::parse_in_arena(ymlfile, ymlsrc);
6637 bool gotit = false;
6638 try
6639 {
6640 int intval = 0;
6641 tree["float"].load(&intval); // cannot deserialize 123.456 to int
6642 }
6643 catch(ryml::ExceptionBasic const& exc) // use references! don't slice the exception
6644 {
6645 gotit = true;
6646 ryml::csubstr msg = ryml::to_csubstr(exc.what());
6648 CHECK(!msg.empty());
6649 }
6650 CHECK(gotit);
6651 }
6652#endif
6653}
6654
6655
6656/** It is possible to obtain the YAML location from a visit
6657 * error: when the tree is obtained from parsing YAML, the messages
6658 * may be enriched by using a parser set to track the locations. See
6659 * @ref sample_location_tracking() for more details on how to use
6660 * locations. */
6662{
6664 // we will use locations to show the YAML source context of the
6665 // node where the visit error was triggered. This is a very
6666 // convenient feature to show detailed messages when deserializing
6667 // data read from a file (but do note this is opt-in, and it is
6668 // not mandatory). See sample_location_tracking() for more details
6669 // on location tracking.
6671 ryml::EventHandlerTree evt_handler{};
6672 ryml::Parser parser(&evt_handler, opts);
6673 ryml::csubstr ymlfile = "file.yml";
6674 ryml::csubstr ymlsrc = ""
6675 "foo: bar" "\n"
6676 "char: a" "\n"
6677 "int: a" "\n"
6678 "float: 123.456" "\n"
6679 "";
6680 const ryml::Tree tree = ryml::parse_in_arena(&parser, ymlfile, ymlsrc);
6681 // This function will cause a visit error when being called:
6682 auto cause_visit_error = [&]{
6683 int intval = 0;
6684 tree["float"].load(&intval); // cannot deserialize 123.456 to int
6685 };
6686 // Like with the parse error, we will use our error handler to
6687 // catch that visit error, and save the error info:
6688 CHECK(evt_handler.callbacks() == errh.callbacks());
6689 CHECK(parser.callbacks() == errh.callbacks());
6690 CHECK(tree.callbacks() == errh.callbacks());
6691 {
6692 CHECK(errh.check_error_occurs(cause_visit_error));
6693 // the handler in errh saves the error info in itself. Let's
6694 // use that to see the messages we get.
6695 //
6696 // this message is the short message passed into the visit error
6697 CHECK(errh.saved_msg_short == "could not deserialize node");
6698 // this message was created inside the handler, by calling
6699 // ryml::err_visit_format():
6700 CHECK(ryml::csubstr::npos != ryml::to_csubstr(errh.saved_msg_full).find("ERROR: [visit] could not deserialize node"));
6701 // The location of the visit error is of the C++ source file where
6702 // the error was detected -- NOT of the YAML source file:
6703 CHECK(errh.saved_basic_loc.name != ymlfile);
6704 // However, note that the tree and node id are available:
6705 CHECK(errh.saved_visit_tree == &tree);
6706 CHECK(errh.saved_visit_id == tree["float"].id());
6707 // ... which we can use to get the location in the YAML source
6708 // from the parser (but see @ref sample_location_tracking()):
6709 ryml::Location ymlloc = errh.saved_visit_tree->location(parser, errh.saved_visit_id);
6710 CHECK(ymlloc.name == ymlfile);
6711 // In turn, we can use format_location_context() to
6712 // print/create an error message pointing at the YAML source
6713 // code:
6714 std::string msg = errh.saved_msg_full;
6716 msg.append(s.str, s.len);
6717 }, ymlloc, ymlsrc, "err", /*number of lines to show before the error*/3);
6718 CHECK(ryml::to_csubstr(msg).ends_with(
6719 "file.yml:3: col=0 (24B): err:" "\n"
6720 "err:" "\n"
6721 "err: float: 123.456" "\n"
6722 "err: |" "\n"
6723 "err: (here)" "\n"
6724 "err:" "\n"
6725 "err: see region:" "\n"
6726 "err:" "\n"
6727 "err: foo: bar" "\n"
6728 "err: char: a" "\n"
6729 "err: int: a" "\n"
6730 "err: float: 123.456" "\n"
6731 "err: |" "\n"
6732 "err: (here)" "\n"
6733 ""));
6734 }
6735}
6736
6737
6738//-----------------------------------------------------------------------------
6739
6740// Please note the following about the use of custom allocators with
6741// ryml. Due to [the static initialization order
6742// fiasco](https://en.cppreference.com/w/cpp/language/siof), if you
6743// use static ryml trees or parsers, you need to make sure that their
6744// callbacks have the same lifetime. So you can't use ryml's default
6745// callbacks structure, as it is declared in a ryml file, and the standard
6746// provides no guarantee on the relative initialization order, such
6747// that it is constructed before and destroyed after your
6748// variables. In fact you are pretty much guaranteed to see this
6749// fail. So please carefully consider your choices, and ponder
6750// whether you really need to use ryml static trees and parsers. If
6751// you do need this, then you will need to declare and use a ryml
6752// callbacks structure that outlives the tree and/or parser.
6753//
6754// See also sample_static_trees() for an example on how to use
6755// trees with static lifetime.
6756
6757/** @addtogroup doc_quickstart_helpers
6758 * @{ */
6760{
6761 std::vector<char> memory_pool = std::vector<char>(10u * 1024u); // 10KB
6762 size_t num_allocs = 0, alloc_size = 0, corr_size = 0;
6764
6765 void *allocate(size_t len)
6766 {
6767 void *ptr = &memory_pool[alloc_size];
6768 alloc_size += len;
6769 ++num_allocs;
6770 // ensure the ptr is aligned
6771 uintptr_t uptr = (uintptr_t)ptr;
6772 const uintptr_t align = alignof(max_align_t);
6773 if (uptr % align)
6774 {
6775 uintptr_t prev = uptr - (uptr % align);
6776 uintptr_t next = prev + align;
6777 uintptr_t corr = next - uptr;
6778 ptr = (void*)(((char*)ptr) + corr);
6779 corr_size += corr;
6780 }
6781 C4_CHECK_MSG(alloc_size + corr_size <= memory_pool.size(),
6782 "out of memory! requested=%zu+%zu available=%zu\n",
6784 return ptr;
6785 }
6786
6787 void free(void *mem, size_t len)
6788 {
6789 CHECK((char*)mem >= &memory_pool.front() && (char*)mem < &memory_pool.back());
6790 CHECK((char*)mem+len >= &memory_pool.front() && (char*)mem+len <= &memory_pool.back());
6791 dealloc_size += len;
6792 ++num_deallocs;
6793 // no need to free here
6794 }
6795
6796 // bridge
6804 static void* s_allocate(size_t len, void* /*hint*/, void *this_)
6805 {
6806 return ((GlobalAllocatorExample*)this_)->allocate(len);
6807 }
6808 static void s_free(void *mem, size_t len, void *this_)
6809 {
6810 ((GlobalAllocatorExample*)this_)->free(mem, len);
6811 }
6812
6813 // checking
6819 {
6820 std::cout << "size: alloc=" << alloc_size << " dealloc=" << dealloc_size << std::endl;
6821 std::cout << "count: #allocs=" << num_allocs << " #deallocs=" << num_deallocs << std::endl;
6823 CHECK(alloc_size >= dealloc_size); // failure here means a double free
6824 CHECK(alloc_size == dealloc_size); // failure here means a leak
6825 num_allocs = 0;
6826 num_deallocs = 0;
6827 alloc_size = 0;
6828 dealloc_size = 0;
6829 }
6830};
6831/** @} */
6832
6833
6834/** demonstrates how to set the global allocator for ryml */
6836{
6838
6839 // save the existing callbacks for restoring
6841
6842 // set to our callbacks
6844
6845 // verify that the allocator is in effect
6846 ryml::Callbacks const& current = ryml::get_callbacks();
6847 CHECK(current.m_allocate == &mem.s_allocate);
6848 CHECK(current.m_free == &mem.s_free);
6849
6850 // so far nothing was allocated
6851 CHECK(mem.alloc_size == 0);
6852
6853 // parse one tree and check
6854 (void)ryml::parse_in_arena(R"({foo: bar})");
6855 mem.check_and_reset();
6856
6857 // parse another tree and check
6858 (void)ryml::parse_in_arena(R"([a, b, c, d, {foo: bar, money: pennys}])");
6859 mem.check_and_reset();
6860
6861 // verify that by reserving we save allocations
6862 {
6863 ryml::EventHandlerTree evt_handler;
6864 ryml::Parser parser(&evt_handler); // reuse a parser
6865 ryml::Tree tree; // reuse a tree
6866
6867 tree.reserve(10); // reserve the number of nodes
6868 tree.reserve_arena(100); // reserve the arena size
6869 parser.reserve_stack(10); // reserve the parser depth.
6870
6871 // since the parser stack uses Small Storage Optimization,
6872 // allocations will only happen with capacities higher than 16.
6873 CHECK(mem.num_allocs == 2); // tree, tree_arena and NOT the parser
6874
6875 parser.reserve_stack(20); // reserve the parser depth.
6876 CHECK(mem.num_allocs == 3); // tree, tree_arena and now the parser as well
6877
6878 // verify that no other allocations occur when parsing
6879 size_t size_before = mem.alloc_size;
6880 parse_in_arena(&parser, "", R"([a, b, c, d, {foo: bar, money: pennys}])", &tree);
6881 CHECK(mem.alloc_size == size_before);
6882 CHECK(mem.num_allocs == 3);
6883 }
6884 mem.check_and_reset();
6885
6886 // restore defaults.
6887 ryml::set_callbacks(defaults);
6888}
6889
6890
6891//-----------------------------------------------------------------------------
6892
6893/** @addtogroup doc_quickstart_helpers
6894 * @{ */
6895
6896/** an example for a per-tree memory allocator */
6898{
6899 std::vector<char> memory_pool = std::vector<char>(10u * 1024u); // 10KB
6900 size_t num_allocs = 0, alloc_size = 0;
6902
6904 {
6905 // Above we used static functions to bridge to our methods.
6906 // To show a different approach, we employ lambdas here.
6907 // Note that there can be no captures in the lambdas
6908 // because these are C-style function pointers.
6909 ryml::Callbacks cb;
6910 cb.m_user_data = (void*) this;
6911 cb.m_allocate = [](size_t len, void *, void *data){ return ((PerTreeMemoryExample*) data)->allocate(len); };
6912 cb.m_free = [](void *mem, size_t len, void *data){ return ((PerTreeMemoryExample*) data)->free(mem, len); };
6913 return cb;
6914 }
6915
6916 void *allocate(size_t len)
6917 {
6918 void *ptr = &memory_pool[alloc_size];
6919 alloc_size += len;
6920 ++num_allocs;
6921 if C4_UNLIKELY(alloc_size > memory_pool.size())
6922 {
6923 std::cerr << "out of memory! requested=" << alloc_size << " vs " << memory_pool.size() << " available" << std::endl; // LCOV_EXCL_LINE
6924 std::abort(); // LCOV_EXCL_LINE
6925 }
6926 return ptr;
6927 }
6928
6929 void free(void *mem, size_t len)
6930 {
6931 CHECK((char*)mem >= &memory_pool.front() && (char*)mem < &memory_pool.back());
6932 CHECK((char*)mem+len >= &memory_pool.front() && (char*)mem+len <= &memory_pool.back());
6933 dealloc_size += len;
6934 ++num_deallocs;
6935 // no need to free here
6936 }
6937
6938 // checking
6944 {
6945 std::cout << "size: alloc=" << alloc_size << " dealloc=" << dealloc_size << std::endl;
6946 std::cout << "count: #allocs=" << num_allocs << " #deallocs=" << num_deallocs << std::endl;
6948 CHECK(alloc_size >= dealloc_size); // failure here means a double free
6949 CHECK(alloc_size == dealloc_size); // failure here means a leak
6950 num_allocs = 0;
6951 num_deallocs = 0;
6952 alloc_size = 0;
6953 dealloc_size = 0;
6954 }
6955};
6956/** @} */
6957
6959{
6963
6964 // the trees will use the memory in the resources above,
6965 // with each tree using a separate resource
6966 {
6967 // Watchout: ensure that the lifetime of the callbacks target
6968 // exceeds the lifetime of the tree.
6969 ryml::EventHandlerTree evt_handler(mrp.callbacks());
6970 ryml::Parser parser(&evt_handler);
6971 ryml::Tree tree1(mr1.callbacks());
6972 ryml::Tree tree2(mr2.callbacks());
6973
6974 ryml::csubstr yml1 = "{a: b}";
6975 ryml::csubstr yml2 = "{c: d, e: f, g: [h, i, 0, 1, 2, 3]}";
6976
6977 parse_in_arena(&parser, "file1.yml", yml1, &tree1);
6978 parse_in_arena(&parser, "file2.yml", yml2, &tree2);
6979 }
6980
6981 CHECK(mrp.num_allocs == 0); // YAML depth not large enough to warrant a parser allocation
6982 CHECK(mr1.alloc_size <= mr2.alloc_size); // because yml2 has more nodes
6983}
6984
6985
6986//-----------------------------------------------------------------------------
6987
6988
6989/** shows how to work around the static initialization order fiasco
6990 * when using a static-duration ryml tree
6991 * @see https://en.cppreference.com/w/cpp/language/siof */
6993{
6994 // Static trees may incur a static initialization order
6995 // problem. This happens because a default-constructed tree will
6996 // obtain the callbacks from the current global setting, which may
6997 // not have been initialized due to undefined static
6998 // initialization order:
6999 //
7000 // ERROR! depends on ryml::get_callbacks() which may not have been initialized.
7001 //static ryml::Tree tree;
7002 //
7003 // To work around the issue, declare static callbacks
7004 // to explicitly initialize the static tree:
7005 static ryml::Callbacks callbacks = default_callbacks(); // use default callback members
7006 static ryml::Tree tree(callbacks); // OK
7007 // now you can use the tree as normal:
7008 ryml::parse_in_arena(R"(doe: "a deer, a female deer")", &tree);
7009 CHECK(tree["doe"].val() == "a deer, a female deer");
7010}
7011
7012
7013//-----------------------------------------------------------------------------
7014//-----------------------------------------------------------------------------
7015//-----------------------------------------------------------------------------
7016
7017
7018/** @addtogroup doc_quickstart_helpers
7019 * @{ */
7020
7021namespace /*anon*/ {
7022static int num_checks = 0;
7023static int num_failed_checks = 0;
7024static bool quiet_mode = false;
7025} // namespace /*anon*/
7026
7027void handle_args(int argc, const char* argv[])
7028{
7029 auto arg_matches = [](const char *arg, const char *shortform, const char *longform) {
7030 return (0 == strcmp(arg, shortform) || 0 == strcmp(arg, longform));
7031 };
7032 for(int i = 1; i < argc; ++i)
7033 if(arg_matches(argv[i], "-q", "--quiet"))
7034 quiet_mode = true;
7035}
7036
7037bool report_check(int line, const char *predicate, bool result)
7038{
7039 ++num_checks;
7040 const char *msg = predicate ? "OK! " : "OK!";
7041 if(!result)
7042 {
7043 ++num_failed_checks; // LCOV_EXCL_LINE
7044 msg = predicate ? "FAIL: " : "FAIL"; // LCOV_EXCL_LINE
7045 }
7046 if(!result || !quiet_mode)
7047 {
7048 std::cout << __FILE__ << ':' << line << ": " << msg << (predicate ? predicate : "") << std::endl;
7049 }
7050 return result;
7051}
7052
7053
7055{
7056 std::cout << "Completed " << num_checks << " checks." << std::endl;
7057 if(num_failed_checks)
7058 std::cout << "ERROR: " << num_failed_checks << '/' << num_checks << " checks failed." << std::endl; // LCOV_EXCL_LINE
7059 else
7060 std::cout << "SUCCESS!" << std::endl;
7061 return num_failed_checks;
7062}
7063
7064
7065//-----------------------------------------------------------------------------
7066// methods to provide default callbacks (needed when
7067// RYML_NO_DEFAULT_CALLBACKS is defined)
7068
7069namespace {
7070// LCOV_EXCL_START
7071/** dump (part of an) error message to terminal
7072 * @ingroup doc_quickstart_helpers */
7073void errdump(ryml::csubstr s)
7074{
7075 if(s.len)
7076 fwrite(s.str, 1, s.len, stderr); // NOLINT
7077}
7078/** finish printing an error message, and flush
7079 * @ingroup doc_quickstart_helpers */
7080void errend()
7081{
7082 fputc('\n', stderr); // NOLINT
7083 fflush(NULL); // NOLINT
7084}
7085// LCOV_EXCL_STOP
7086} // namespace
7087
7088
7089/** set up a bare-bones implementation of the callbacks
7090 * @ingroup doc_quickstart_helpers */
7092{
7093 return ryml::Callbacks{}
7094 .set_allocate([](size_t len, void* , void *){
7095 return malloc(len); // NOLINT
7096 })
7097 .set_free([](void* mem, size_t, void *){
7098 free(mem); // NOLINT
7099 })
7100 ///
7101 /// The default error callbacks won't be called in this
7102 /// quickstart, because no errors are expected. But we
7103 /// implement them here to show how a bare-bones implementation
7104 /// looks like, and also because they are needed when
7105 /// @ref RYML_NO_DEFAULT_CALLBACKS is defined.
7106 ///
7107 /// For a different (more involved) implementation of the error
7108 /// callbacks, see the implementation of @ref ErrorHandlerExample
7109 /// below.
7110 ///
7111 // LCOV_EXCL_START
7112 .set_error_basic([](ryml::csubstr msg, ryml::ErrorDataBasic const& errdata, void *){
7113 ryml::err_basic_format(errdump, msg, errdata); // format the message, printing to stderr
7114 errend(); // print newline and flush
7115 abort(); // abort (must never return: abort, or exception or setjmp)
7116 })
7117 .set_error_parse([](ryml::csubstr msg, ryml::ErrorDataParse const& errdata, void *){
7118 ryml::err_parse_format(errdump, msg, errdata); // format the message, printing to out
7119 errend(); // print newline and flush
7120 abort(); // abort (must never return: abort, or exception or setjmp)
7121 })
7122 .set_error_visit([](ryml::csubstr msg, ryml::ErrorDataVisit const& errdata, void *){
7123 ryml::err_visit_format(errdump, msg, errdata); // format the message, printing to out
7124 errend(); // print newline and flush
7125 abort(); // abort (must never return: abort, or exception or setjmp)
7126 });
7127 // LCOV_EXCL_STOP
7128}
7129
7130/** set up default callbacks when ryml does not provide them
7131 * (ie when @ref RYML_NO_DEFAULT_CALLBACKS is defined)
7132 * @ingroup doc_quickstart_helpers */
7134{
7135#ifdef RYML_NO_DEFAULT_CALLBACKS
7137#endif
7138}
7139
7140
7141//-----------------------------------------------------------------------------
7142// methods for the example error handler
7143
7144#ifndef C4_EXCEPTIONS
7145/*environment for setjmp*/
7146static std::jmp_buf s_jmp_env;
7147static std::string s_jmp_msg;
7148#endif
7149
7150/** checking that an assertion occurs while calling fn. assertions are
7151 * enabled if @ref RYML_USE_ASSERT is defined.
7152 * @ingroup doc_quickstart_helpers */
7153template<class Fn>
7155{
7156 #if RYML_USE_ASSERT
7157 return check_error_occurs(std::forward<Fn>(fn));
7158 #else
7159 (void)fn; // do nothing otherwise, as there would be undefined behavior
7160 return true;
7161 #endif
7162}
7163/** checking that an error occurs while calling fn
7164 * @ingroup doc_quickstart_helpers */
7165template<class Fn>
7167{
7168 saved_msg_short.clear();
7169 saved_msg_full.clear();
7171 saved_basic_loc = {};
7172 saved_parse_loc = {};
7173 saved_visit_tree = {};
7175 bool got_error = false;
7176 #ifdef C4_EXCEPTIONS
7177 try
7178 {
7179 std::forward<Fn>(fn)();
7180 }
7181 catch(std::exception const&)
7182 {
7183 got_error = true;
7184 }
7185 #else
7186 if(setjmp(s_jmp_env) == 0)
7187 {
7188 std::forward<Fn>(fn)();
7189 }
7190 else
7191 {
7192 got_error = true;
7193 }
7194 #endif
7195 return got_error;
7196}
7197
7198namespace {
7199/** interrupt execution
7200 * @ingroup doc_quickstart_helpers */
7201[[noreturn]] void stopexec(std::string const& s)
7202{
7203 #ifdef C4_EXCEPTIONS
7204 throw std::runtime_error(s);
7205 #else
7206 s_jmp_msg = s;
7207 std::longjmp(s_jmp_env, 1); // jump to the corresponding call to setjmp().
7208 #endif
7209}
7210} // namespace
7211/** this is where the callback implementation goes. Remember that it must not return.
7212 * @ingroup doc_quickstart_helpers
7213 * */
7215{
7216 saved_msg_short.assign(msg.str, msg.len);
7217 // build a full error message with location
7219 saved_msg_full.append(s.str, s.len);
7220 }, msg, errdata);
7221 // Save the error params for subsequent testing in the quickstart.
7223 saved_basic_loc = errdata.location;
7224 stopexec(saved_msg_short);
7225}
7226/** this is where the callback implementation goes. Remember that it must not return.
7227 * @ingroup doc_quickstart_helpers
7228 * @see ryml::format_location_context
7229 * */
7231{
7232 saved_msg_short.assign(msg.str, msg.len);
7233 // build a full error message with location
7235 saved_msg_full.append(s.str, s.len);
7236 }, msg, errdata);
7237 // Save the error params for subsequent testing in the quickstart.
7238 //
7239 // To add the source context, the source buffer is required. If
7240 // the caller is interested in enriching the full message with the
7241 // source buffer context, he can ensure that the source buffer is
7242 // kept, and then arrange the handler to access it.
7243 // For now, we assign the full message without context:
7245 saved_basic_loc = errdata.cpploc;
7246 saved_parse_loc = errdata.ymlloc;
7247 stopexec(saved_msg_full);
7248}
7249/** this is where the callback implementation goes. Remember that it must not return.
7250 * @ingroup doc_quickstart_helpers
7251 * */
7253{
7254 saved_msg_short.assign(msg.str, msg.len);
7255 // build a full error message with location
7257 saved_msg_full.append(s.str, s.len);
7258 }, msg, errdata);
7259 // Save the error params for subsequent testing in the quickstart.
7260 //
7261 // To add the source context, the source buffer is required. If
7262 // the caller is interested in enriching the full message with the
7263 // source buffer context, he can ensure that the source buffer is
7264 // kept, and then arrange the handler to access it.
7265 // For now, we assign the full message without context:
7267 saved_basic_loc = errdata.cpploc;
7268 saved_visit_tree = errdata.tree;
7269 saved_visit_id = errdata.node;
7270 stopexec(saved_msg_full);
7271}
7272
7273/** trampoline function to call the object's method */
7274[[noreturn]] void ErrorHandlerExample::s_error_basic(ryml::csubstr msg, ryml::ErrorDataBasic const& errdata, void *this_)
7275{
7276 static_cast<ErrorHandlerExample*>(this_)->on_error_basic(msg, errdata);
7277}
7278/** trampoline function to call the object's method */
7279[[noreturn]] void ErrorHandlerExample::s_error_parse(ryml::csubstr msg, ryml::ErrorDataParse const& errdata, void *this_)
7280{
7281 static_cast<ErrorHandlerExample*>(this_)->on_error_parse(msg, errdata);
7282}
7283/** trampoline function to call the object's method */
7284[[noreturn]] void ErrorHandlerExample::s_error_visit(ryml::csubstr msg, ryml::ErrorDataVisit const& errdata, void *this_)
7285{
7286 static_cast<ErrorHandlerExample*>(this_)->on_error_visit(msg, errdata);
7287}
7288
7289
7290/** a helper to create the Callbacks object for the custom error handler
7291 * @ingroup doc_quickstart_helpers
7292 * */
7302
7303/** test that this handler is currently set */
7305{
7306 ryml::Callbacks const& current = ryml::get_callbacks();
7307 CHECK(current.m_error_basic == &s_error_basic);
7308 CHECK(current.m_error_parse == &s_error_parse);
7309 CHECK(current.m_error_visit == &s_error_visit);
7310 CHECK(current.m_allocate == original_callbacks.m_allocate);
7311 CHECK(current.m_free == original_callbacks.m_free);
7312}
7313/** test that this handler is currently not set */
7315{
7316 ryml::Callbacks const& current = ryml::get_callbacks();
7317 CHECK(current.m_error_basic != &s_error_basic);
7318 CHECK(current.m_error_parse != &s_error_parse);
7319 CHECK(current.m_error_visit != &s_error_visit);
7320 CHECK(current.m_allocate == original_callbacks.m_allocate);
7321 CHECK(current.m_free == original_callbacks.m_free);
7322}
7323
7324
7325/** @} */ // doc_quickstart_helpers
7326
7327
7328C4_SUPPRESS_WARNING_GCC_CLANG_POP
Holds a pointer to an existing tree, and a node id.
Definition node.hpp:737
ConstNodeRef parent() const RYML_NOEXCEPT
Forward to Tree::parent().
Definition node.hpp:853
ConstNodeRef child(id_type pos) const RYML_NOEXCEPT
Forward to Tree::child().
Definition node.hpp:856
ConstNodeRef first_sibling() const RYML_NOEXCEPT
Forward to Tree::first_sibling().
Definition node.hpp:860
id_type id() const noexcept
Definition node.hpp:822
ConstNodeRef first_child() const RYML_NOEXCEPT
Forward to Tree::first_child().
Definition node.hpp:854
ConstNodeRef last_child() const RYML_NOEXCEPT
Forward to Tree::last_child().
Definition node.hpp:855
bool invalid() const noexcept
Definition node.hpp:790
const_children_view children() const RYML_NOEXCEPT
get an iterable view over children
Definition node.hpp:987
ConstNodeRef last_sibling() const RYML_NOEXCEPT
Forward to Tree::last_sibling().
Definition node.hpp:861
Tree const * tree() const noexcept
Definition node.hpp:821
ConstNodeRef next_sibling() const RYML_NOEXCEPT
Forward to Tree::next_sibling().
Definition node.hpp:859
ConstNodeRef prev_sibling() const RYML_NOEXCEPT
Forward to Tree::prev_sibling().
Definition node.hpp:858
bool readable() const noexcept
because a ConstNodeRef cannot be used to write to the tree, readable() has the same meaning as !...
Definition node.hpp:793
A reference to a node in an existing yaml tree, offering a more convenient API than the index-based A...
Definition node.hpp:1063
void set_style_conditionally(NodeType type_mask, NodeType rem_style_flags, NodeType add_style_flags, bool recurse=false)
Definition node.hpp:1311
void save(T const &k)
Definition node.hpp:1337
void set_serialized(T const &v)
serialize a variable to this node.
Definition node.hpp:1375
void clear_style(bool recurse=false)
Definition node.hpp:1305
children_view children() RYML_NOEXCEPT
get an iterable view over children
Definition node.hpp:1843
void set_val(csubstr val)
Definition node.hpp:1251
void set_key_serialized(T const &k)
serialize a variable, then assign the result to the node's key
Definition node.hpp:1396
void set_container_style(type_bits style)
Definition node.hpp:1267
id_type id() const noexcept
Definition node.hpp:1208
bool invalid() const noexcept
true if the object is not referring to any existing or seed node.
Definition node.hpp:1134
void set_key_style(type_bits style)
Definition node.hpp:1268
bool readable() const noexcept
true if the object is not invalid and not in seed state.
Definition node.hpp:1138
NodeRef append_child()
Definition node.hpp:1715
void set_val_style(type_bits style)
Definition node.hpp:1269
bool is_seed() const noexcept
true if the object is not invalid and in seed state.
Definition node.hpp:1136
void set_val_ref(csubstr val_ref)
Definition node.hpp:1265
void reserve_stack(id_type capacity)
Reserve a certain capacity for the parsing stack.
csubstr location_contents(Location const &loc) const
Get the string starting at a particular location, to the end of the parsed source buffer.
Location val_location(const char *val) const
Given a pointer to a buffer position, get the location.
ParserOptions const & options() const
Get the options used to build this parser object.
Callbacks const & callbacks() const
Get the current callbacks in the parser.
void reserve_locations(size_t num_source_lines)
Reserve a certain capacity for the array used to track node locations in the source buffer.
void clear()
clear the tree and zero every node
Definition tree.cpp:330
csubstr const & key(id_type node) const
Definition tree.hpp:454
id_type first_child(id_type node) const
Definition tree.hpp:577
bool is_stream(id_type node) const
Definition tree.hpp:477
void set_val_ref(id_type node, csubstr ref)
Definition tree.hpp:748
id_type root_id() const
Get the id of the root node. The tree must not be empty. The tree can be empty only when constructed ...
Definition tree.hpp:386
NodeRef rootref()
Get the root as a NodeRef . Note that a non-const Tree implicitly converts to NodeRef.
Definition tree.cpp:56
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:1553
void save(id_type node, T const &val)
Definition tree.hpp:772
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:1409
void set_val(id_type node, csubstr val) RYML_NOEXCEPT
Definition tree.hpp:688
bool is_map(id_type node) const
Definition tree.hpp:480
void set_serialized(id_type node, T const &val) RYML_NOEXCEPT
Definition tree.hpp:823
void set_key_serialized(id_type node, T const &key) RYML_NOEXCEPT
Definition tree.hpp:837
void reserve(id_type node_capacity=RYML_DEFAULT_TREE_CAPACITY)
Definition tree.cpp:292
void load(id_type node, T *v, bool check_readable=true) const
(1) deserialize the node's contents (val or container) to the given variable, forwarding to the user-...
Definition tree.hpp:871
csubstr const & val(id_type node) const
Definition tree.hpp:460
void clear_arena()
Definition tree.hpp:340
bool is_root(id_type node) const
Definition tree.hpp:529
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:1397
bool in_arena(csubstr s) const
return true if the given substring is part of the tree's string arena
Definition tree.hpp:1327
void load_key(id_type node, T *k, bool check_readable=true) const
(1) deserialize the node's key (necessarily a scalar) to the given variable, forwarding to the user-o...
Definition tree.hpp:912
void set_seq(id_type node) RYML_NOEXCEPT
Definition tree.hpp:720
id_type append_child(id_type parent)
create and insert a node as the last child of parent
Definition tree.hpp:1180
void clear_style(id_type node, bool recurse=false)
Definition tree.cpp:1404
ReadResult deserialize(id_type node, T *v) const
(1) deserialize a node's contents to a variable
Definition tree.hpp:954
id_type next_sibling(id_type node) const
Definition tree.hpp:572
ConstNodeRef crootref() const
Get the root as a ConstNodeRef . Note that Tree implicitly converts to ConstNodeRef.
Definition tree.cpp:65
void save_key(id_type node, T const &key)
Definition tree.hpp:794
void set_key_tag(id_type node, csubstr tag)
Definition tree.hpp:742
void set_container_style(id_type node, type_bits style)
Definition tree.hpp:658
id_type id(NodeData const *n) const
get the id of a node belonging to this tree. n can be nullptr, in which case NONE is returned n must ...
Definition tree.hpp:392
void set_style_conditionally(id_type node, NodeType type_mask, NodeType rem_style_flags, NodeType add_style_flags, bool recurse=false)
Definition tree.cpp:1414
void normalize_tags()
Definition tree.cpp:1571
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:1349
void set_val_anchor(id_type node, csubstr anchor)
Definition tree.hpp:746
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:478
Callbacks const & callbacks() const
Definition tree.hpp:349
size_t arena_capacity() const
get the current capacity of the tree's internal arena
Definition tree.hpp:1311
id_type doc(id_type i) const
gets the i document node index.
Definition tree.hpp:605
void normalize_tags_long()
Definition tree.cpp:1578
void resolve(ReferenceResolver *rr, bool clear_anchors=true)
Resolve references (aliases <- anchors), by forwarding to ReferenceResolver::resolve(); refer to Refe...
Definition tree.cpp:1206
bool is_seq(id_type node) const
Definition tree.hpp:481
void set_map(id_type node) RYML_NOEXCEPT
Definition tree.hpp:731
ReadResult deserialize_key(id_type node, T *v) const
(1) deserialize a node's key to a variable
Definition tree.hpp:975
id_type find_child(id_type node, csubstr const &key) const
find child by name, or NONE if no child is found with this key like Tree::child(),...
Definition tree.cpp:1249
Location location(Parser const &p, id_type node) const
Get the location of a node from the parse used to parse this tree.
Definition tree.cpp:1913
void add_tag_directive(csubstr handle, csubstr prefix, id_type id)
Definition tree.cpp:1444
id_type first_sibling(id_type node) const
Definition tree.hpp:592
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:1370
void set_key(id_type node, csubstr key) RYML_NOEXCEPT
Definition tree.hpp:705
id_type num_children(id_type node) const
O(num_children).
Definition tree.cpp:1216
csubstr arena() const
get the current arena
Definition tree.hpp:1317
id_type size() const
Definition tree.hpp:345
id_type child(id_type node, id_type pos) const
find child by position, or NONE if there are less than pos children posi
Definition tree.cpp:1237
void set_val_tag(id_type node, csubstr tag)
Definition tree.hpp:743
void set_key_anchor(id_type node, csubstr anchor)
Definition tree.hpp:745
ReadResult deserialize_child(id_type node, csubstr child_key, T *v) const
(1) find a child by name and deserialize its contents to the given variable (ie call ....
Definition tree.hpp:1011
Definitions of error utilities used by ryml.
provides type-safe facilities for formatting arguments to string buffers
Utilities for formatting data as base64.
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
const_base64_wrapper base64(csubstr s, size_t *reqsize=nullptr)
a tag function to mark a csubstr payload to be encoded in base64 format
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
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
Callbacks const & get_callbacks()
get the global callbacks
Definition common.cpp:94
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_FLOAT
print the real number in floating point format (like f)
Definition charconv.hpp:194
@ FTOA_HEXA
print the real number in hexadecimal format (like a)
Definition charconv.hpp:200
substr emit_yaml(Tree const &t, EmitOptions const &opts, substr buf, bool error_on_excess)
(1) emit YAML to the given buffer.
Definition emit_buf.cpp:28
substr emitrs_json(Tree const &t, id_type id, EmitOptions const &opts, CharOwningContainer *cont, bool append=false)
(1) emit+resize: emit JSON to the given std::string/std::vector<char>-like container,...
substr emitrs_yaml(Tree const &t, id_type id, EmitOptions const &opts, CharOwningContainer *cont, bool append=false)
(1) emit+resize: emit YAML to the given std::string/std::vector<char>-like container,...
void err_visit_format(DumpFn &&dumpfn, csubstr msg, ErrorDataVisit const &errdata)
Given an error message and associated visit error data, format it fully as a visit error message.
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_basic_format(DumpFn &&dumpfn, csubstr msg, ErrorDataBasic const &errdata)
Given an error message and associated basic error data, format it fully as a basic error message.
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.
void file_get_contents(const char *filename, FILE *fp, size_t filesz, void *buf, size_t bufsz)
load a file of specified size from disk into an existing contiguous buffer.
Definition file.hpp:106
void file_put_contents(void const *buf, size_t sz, FILE *file, const char *filename=nullptr)
save a contiguous buffer into a file
Definition file.hpp:57
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
@ KEY_DQUO
mark key scalar as double quoted "
@ MAP
a map: a parent of KEYVAL/KEYSEQ/KEYMAP nodes
Definition node_type.hpp:35
@ KEY
the scalar to the left of : in a map's member
Definition node_type.hpp:33
@ FLOW_ML1
mark container with multi-line flow style, 1 element per line
Definition node_type.hpp:75
@ VAL_FOLDED
mark val scalar as multiline, block folded >
@ 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:56
@ 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:34
@ FLOW_MLN
mark container with multi-line flow style, n elements per line, wrapped (as set by EmitOptions::max_c...
Definition node_type.hpp:90
@ SEQ
a seq: a parent of VAL/SEQ/MAP nodes
Definition node_type.hpp:36
@ 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 >
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...
void parse_in_arena(Parser *parser, csubstr filename, csubstr yaml, Tree *tree, id_type node_id)
(1) parse YAML into an existing tree node. The filename will be used in any error messages arising du...
Definition parse.cpp:209
void parse_json_in_arena(Parser *parser, csubstr filename, csubstr json, Tree *tree, id_type node_id)
(1) parse JSON into an existing tree node. The filename will be used in any error messages arising du...
Definition parse.cpp:231
void parse_in_place(Parser *parser, csubstr filename, substr yaml, Tree *tree, id_type node_id)
(1) parse YAML into an existing tree node.
Definition parse.cpp:165
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
void sample_per_tree_allocator()
set per-tree allocators
void sample_global_allocator()
set a global allocator for ryml
void sample_anchors_and_aliases()
deal with YAML anchors and aliases
void sample_anchors_and_aliases_create()
how to create YAML anchors and aliases
void sample_docs()
deal with YAML docs
void sample_emit_to_file()
emit to a FILE*
void sample_emit_nested_node()
pick a nested node as the root when emitting
void sample_emit_to_container()
emit to memory, eg a string or vector-like container
void sample_emit_to_stream()
emit to a stream, eg std::ostream
void sample_error_visit()
handler for visit errors, and obtain a full error message with visit context
void sample_error_parse()
handler for parse errors, and obtain a full error message with parse context
void sample_error_visit_location()
obtaining the YAML location from a visit error
void sample_error_basic()
handler for basic errors, and obtain a full error message with basic context
void sample_error_handler()
set custom error handlers
static void s_error_basic(ryml::csubstr msg, ryml::ErrorDataBasic const &errdata, void *this_)
trampoline function to call the object's method
bool report_check(int line, const char *predicate, bool result)
used by CHECK()
static void s_error_parse(ryml::csubstr msg, ryml::ErrorDataParse const &errdata, void *this_)
trampoline function to call the object's method
bool check_assertion_occurs(Fn &&fn)
checking that an assertion occurs while calling fn.
void on_error_visit(ryml::csubstr msg, ryml::ErrorDataVisit const &errdata)
this is where the callback implementation goes.
static std::string s_jmp_msg
int report_checks()
void ensure_callbacks()
set up default callbacks when ryml does not provide them (ie when RYML_NO_DEFAULT_CALLBACKS is define...
static void s_error_visit(ryml::csubstr msg, ryml::ErrorDataVisit const &errdata, void *this_)
trampoline function to call the object's method
void on_error_parse(ryml::csubstr msg, ryml::ErrorDataParse const &errdata)
this is where the callback implementation goes.
bool check_error_occurs(Fn &&fn)
checking that an error occurs while calling fn
ryml::Callbacks default_callbacks()
set up a bare-bones implementation of the callbacks
void check_disabled() const
test that this handler is currently not set
#define CHECK(predicate)
a testing assertion, used only in this quickstart
ryml::Callbacks callbacks()
a helper to create the Callbacks object for the custom error handler
void on_error_basic(ryml::csubstr msg, ryml::ErrorDataBasic const &errdata)
this is where the callback implementation goes.
static std::jmp_buf s_jmp_env
void check_enabled() const
test that this handler is currently set
void handle_args(int argc, const char *argv[])
void sample_json()
JSON parsing and emitting.
void sample_quick_overview()
quick overview of most common features
void sample_lightning_overview()
lightning overview of most common features
void sample_parse_style()
shows how rapidyaml retains the style of parsed YAML
void sample_parse_file()
ready-to-go example of parsing a file from disk
void sample_parse_in_arena()
parse a read-only YAML source buffer
void sample_parse_reuse_parser()
reuse an existing parser
void sample_parse_reuse_tree_and_parser()
how to reuse existing trees and parsers
void sample_parse_in_place()
parse a mutable YAML source buffer
void sample_parse_reuse_tree()
parse into an existing tree, maybe into a node
void sample_formatting()
control formatting when serializing/deserializing
void sample_user_container_types()
serialize/deserialize container (map or seq) types
void sample_empty_null_values()
serialize/deserialize/query empty or null values
void sample_base64()
encode/decode base64
void sample_serialize_basic()
serialize/deserialize fundamental types
void sample_fundamental_types()
serialize/deserialize fundamental types
void sample_std_types()
serialize/deserialize STL containers
void sample_float_precision()
control precision of serialized floats
void sample_user_scalar_types()
serialize/deserialize scalar (leaf/scalar) types
void sample_deserialize_error()
shows error on deserializing nested nodes
void sample_static_trees()
how to use static trees in ryml
void sample_style_flow_ml_indent()
control indentation of FLOW_ML1 and FLOW_MLN containers
void sample_style()
query/set node styles
void sample_style_flow_formatting()
control formatting of flow containers
void sample_substr()
about ryml's string views (from c4core)
void sample_tag_directives()
deal with YAML tag namespace directives
void sample_tags()
deal with YAML type tags
void sample_create_tree_style()
set node styles while creating trees
void sample_location_tracking()
track node YAML source locations in the parsed tree
void sample_create_tree()
programatically create trees
void sample_iterate_tree()
visit individual nodes and iterate through trees
void sample_tree_arena()
interact with the tree's serialization arena
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
ryml::ReadResult read(ryml::Tree const *tree, ryml::id_type id, my_seq_type< T > *seq)
void write(ryml::Tree *tree, ryml::id_type id, my_seq_type< T > const &seq)
size_t to_chars(ryml::substr buf, vec2< T > v)
bool from_chars(ryml::csubstr buf, vec2< T > *v)
bool scalar_is_null(csubstr s) noexcept
YAML-sense query of nullity.
substr to_substr(char(&s)[N]) noexcept
Definition substr.hpp:2376
csubstr to_csubstr(const char(&s)[N]) noexcept
Definition substr.hpp:2380
basic_substring< char > substr
a mutable string view
Definition substr.hpp:2355
basic_substring< const char > csubstr
an immutable string view
Definition substr.hpp:2356
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
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
@ npos
a null string position
Definition common.hpp:138
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:124
@ NONE
an index to none
Definition common.hpp:131
Definition ryml.hpp:6
int main(int argc, const char *argv[])
an error handler used by some of the quickstart examples.
ryml::Location saved_basic_loc
ryml::Callbacks original_callbacks
std::string saved_msg_full
ryml::id_type saved_visit_id
std::string saved_msg_short
ryml::Tree const * saved_visit_tree
ryml::Location saved_parse_loc
std::string saved_msg_full_with_context
void free(void *mem, size_t len)
std::vector< char > memory_pool
static void s_free(void *mem, size_t len, void *this_)
void * allocate(size_t len)
ryml::Callbacks callbacks()
static void * s_allocate(size_t len, void *, void *this_)
an example for a per-tree memory allocator
std::vector< char > memory_pool
ryml::Callbacks callbacks() const
void * allocate(size_t len)
void free(void *mem, size_t len)
Shows how to create a scoped error handler.
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:883
basic_substring trim(const C c) const
trim the character c left and right
Definition substr.hpp:677
size_t count(const C c, size_t pos=0) const
count the number of occurrences of c
Definition substr.hpp:744
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:2209
basic_substring range(size_t first, size_t last=npos) const noexcept
return [first,last[.
Definition substr.hpp:519
size_t first_not_of(const C c) const
Definition substr.hpp:993
auto tolower() -> typename std::enable_if< !std::is_const< U >::value, void >::type
convert the string to lower-case
Definition substr.hpp:2139
bool begins_with(const C c) const noexcept
true if the first character of the string is c
Definition substr.hpp:850
basic_substring triml(const C c) const
trim left
Definition substr.hpp:629
size_t last_of(const C c, size_t start=npos) const
Definition substr.hpp:946
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:547
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:2153
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:536
bool ends_with(const C c) const noexcept
true if the last character of the string is c
Definition substr.hpp:894
size_t first_of(const C c, size_t start=0) const
Definition substr.hpp:934
basic_substring stripl(ro_substr pattern) const
remove a pattern from the left
Definition substr.hpp:690
size_t find(const C c, size_t start_pos=0) const
Definition substr.hpp:713
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:2272
basic_substring stripr(ro_substr pattern) const
remove a pattern from the right
Definition substr.hpp:699
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:493
basic_substring first(size_t num) const noexcept
return the first num elements: [0,num[
Definition substr.hpp:529
basic_substring left_of(size_t pos) const noexcept
return [0, pos[ .
Definition substr.hpp:556
basic_substring sub(size_t first) const noexcept
return [first,len[
Definition substr.hpp:502
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:922
bool empty() const noexcept
Definition substr.hpp:356
C & back() noexcept
Definition substr.hpp:375
basic_substring trimr(const C c) const
trim the character c from the right
Definition substr.hpp:653
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:2221
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:484
size_t last_not_of(const C c) const
Definition substr.hpp:1014
auto toupper() -> typename std::enable_if< ! std::is_const< U >::value, void >::type
convert the string to upper-case
Definition substr.hpp:2127
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:574
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:478
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:772
auto reverse() -> typename std::enable_if< !std::is_const< U >::value, void >::type
reverse in place
Definition substr.hpp:2199
A c-style callbacks class to customize behavior on errors or allocation.
Definition common.hpp:374
pfn_error_basic m_error_basic
a pointer to a basic error handler function
Definition common.hpp:378
pfn_error_parse m_error_parse
a pointer to a parse error handler function
Definition common.hpp:379
Callbacks & set_error_visit(pfn_error_visit error_visit=nullptr)
Set or reset the error_visit callback.
Definition common.cpp:186
Callbacks & set_free(pfn_free free=nullptr)
Set or reset the free callback.
Definition common.cpp:159
void * m_user_data
data to be forwarded in every call to a callback
Definition common.hpp:375
pfn_allocate m_allocate
a pointer to an allocate handler function
Definition common.hpp:376
Callbacks & set_error_parse(pfn_error_parse error_parse=nullptr)
Set or reset the error_parse callback.
Definition common.cpp:177
Callbacks & set_allocate(pfn_allocate allocate=nullptr)
Set or reset the allocate callback.
Definition common.cpp:150
Callbacks & set_error_basic(pfn_error_basic error_basic=nullptr)
Set or reset the error_basic callback.
Definition common.cpp:168
pfn_error_visit m_error_visit
a pointer to a visit error handler function
Definition common.hpp:380
pfn_free m_free
a pointer to a free handler function
Definition common.hpp:377
Callbacks & set_user_data(void *user_data)
Set the user data.
Definition common.cpp:144
A lightweight object containing options to be used when emitting.
EmitOptions & max_cols(id_type cols) noexcept
Set max columns for the emitted YAML in FLOW_MLN mode.
EmitOptions & json_err_on_stream(bool enabled) noexcept
Whether to trigger an error (or emit as seq) when finding a stream in json mode.
bool indent_flow_ml() const noexcept
Indent the contents of FLOW_ML1 and FLOW_MLN containers.
EmitOptions & emit_nonroot_key(bool enabled) noexcept
When emit starts on a nested (non-root) node, emit the node's key as well.
EmitOptions & emit_nonroot_dash(bool enabled) noexcept
When emit starts on a nested (non-root) node, emit a leading dash.
EmitOptions & force_flow_spc(bool enabled) noexcept
Force everywhere a space after comma in flow mode, overriding the FLOW_SPC status of individual conta...
Data for a basic error.
Definition common.hpp:260
Location location
location where the error was detected (may be from YAML or C++ source code)
Definition common.hpp:261
Data for a parse error.
Definition common.hpp:269
Location cpploc
location in the C++ source file where the error was detected.
Definition common.hpp:270
Location ymlloc
location in the YAML source buffer where the error was detected.
Definition common.hpp:271
Data for a visit error.
Definition common.hpp:279
Location cpploc
location in the C++ source file where the error was detected.
Definition common.hpp:280
Tree const * tree
tree where the error was detected
Definition common.hpp:281
id_type node
node where the error was detected
Definition common.hpp:282
The event handler to create a ryml Tree.
Callbacks const & callbacks() const
Exception thrown by the default basic error implementation.
Definition error.hpp:475
const char * what() const noexcept override
Definition error.hpp:477
ErrorDataBasic errdata_basic
error data
Definition error.hpp:478
Exception thrown by the default parse error implementation.
Definition error.hpp:494
ErrorDataParse errdata_parse
Definition error.hpp:496
Exception thrown by the default visit error implementation.
Definition error.hpp:511
ErrorDataVisit errdata_visit
Definition error.hpp:513
holds a source or yaml file position, for example when an error is detected; See also location_format...
Definition common.hpp:229
size_t col
column
Definition common.hpp:232
size_t line
line
Definition common.hpp:231
size_t offset
number of bytes from the beginning of the source buffer
Definition common.hpp:230
csubstr name
name of the file
Definition common.hpp:233
bool is_flow_mln() const noexcept
Options to give to the ParseEngine to control its behavior.
ParserOptions & detect_flow_ml(bool enabled) noexcept
enable/disable detection of flow multiline container style.
ParserOptions & locations(bool enabled) noexcept
enable/disable source location tracking.
ParserOptions & resolve_tags(bool enabled) noexcept
enable/disable resolution of YAML tags during parsing.
ParserOptions & flow_ml_style(NodeType style) noexcept
choose the default style of multiline flow containers, when a container is detected as flow multiline...
A lightweight truthy type, used to enable reporting the offending node when a deserializing error hap...
Definition common.hpp:162
Accelerator structure to reduce memory requirements by enabling reuse of resolved tags.
Definition tag.hpp:71
tag type to mark a tree or node to be emitted as yaml when using operator<<, with options.
bool is_val() const RYML_NOEXCEPT
Forward to Tree::is_val().
Definition node.hpp:211
void load_key(T *k, bool check_readable=true) const
(1) deserialize the node's key (necessarily a scalar) to the given variable, forwarding to the user-o...
Definition node.hpp:385
bool is_root() const RYML_NOEXCEPT
Forward to Tree::is_root().
Definition node.hpp:278
bool is_key_plain() const RYML_NOEXCEPT
Forward to Tree::is_key_plain().
Definition node.hpp:262
bool is_stream() const RYML_NOEXCEPT
Forward to Tree::is_stream().
Definition node.hpp:204
NodeType type() const RYML_NOEXCEPT
Forward to Tree::type().
Definition node.hpp:172
bool has_child(ConstImpl const &n) const RYML_NOEXCEPT
Forward to Tree::has_child().
Definition node.hpp:282
bool is_map() const RYML_NOEXCEPT
Forward to Tree::is_map().
Definition node.hpp:207
id_type num_siblings() const RYML_NOEXCEPT
O(num_children).
Definition node.hpp:303
bool has_key() const RYML_NOEXCEPT
Forward to Tree::has_key().
Definition node.hpp:210
csubstr key() const RYML_NOEXCEPT
Forward to Tree::key().
Definition node.hpp:174
csubstr val() const RYML_NOEXCEPT
Forward to Tree::val().
Definition node.hpp:179
ReadResult deserialize_child(csubstr child_key, T *v) const
(1) find a child by name and deserialize its contents to the given variable (ie call ....
Definition node.hpp:485
bool is_doc() const RYML_NOEXCEPT
Forward to Tree::is_doc().
Definition node.hpp:205
id_type num_children() const RYML_NOEXCEPT
O(num_children).
Definition node.hpp:302
ReadResult deserialize(T *v) const
(1) deserialize the node's contents (val or container) to the given variable, forwarding to the user-...
Definition node.hpp:428
bool is_block() const RYML_NOEXCEPT
Forward to Tree::is_block().
Definition node.hpp:242
Location location(Parser const &parser) const
Definition node.hpp:318
bool is_val_plain() const RYML_NOEXCEPT
Forward to Tree::is_val_plain().
Definition node.hpp:263
bool is_seq() const RYML_NOEXCEPT
Forward to Tree::is_seq().
Definition node.hpp:208
void load(T *v, bool check_readable=true) const
(1) deserialize the node's contents (val or container) to the given variable, forwarding to the user-...
Definition node.hpp:345
bool has_val() const RYML_NOEXCEPT
Forward to Tree::has_val().
Definition node.hpp:209
example scalar type, serialized only
example scalar type, serialized only
example scalar type, serialized only
example user container type: map-like
void check_eq(my_map_type const &that) const
std::map< K, V > map_member
example user container type: seq-like
std::vector< T > seq_member
void check_eq(my_seq_type const &that) const
example user container type with nested user types.
void check_eq(my_type const &that) const
vec2< int > v2
my_map_type< int, int > map
vec3< int > v3
vec4< int > v4
my_seq_type< int > seq
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