rapidyaml 0.16.0
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.16.0/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.16.0?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.16.0
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 -B build
66 * # build and run
67 * cmake --build build --target run -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_brief(); ///< serialize/deserialize container (map or seq) types: brief version
121void sample_user_container_types(); ///< serialize/deserialize container (map or seq) types
122void sample_std_types(); ///< serialize/deserialize STL containers
123void sample_deserialize_error(); ///< shows error on deserializing nested nodes
124void sample_float_precision(); ///< control precision of serialized floats
125/** @} */
126/** @defgroup doc_quickstart_emit Emitting
127 * @ingroup doc_quickstart
128 * @{ */
129void sample_emit_to_container(); ///< emit to memory, eg a string or vector-like container
130void sample_emit_to_stream(); ///< emit to a stream, eg std::ostream
131void sample_emit_to_file(); ///< emit to a FILE*
132void sample_emit_nested_node(); ///< pick a nested node as the root when emitting
133/** @} */
134/** @defgroup doc_quickstart_json JSON
135 * @ingroup doc_quickstart
136 * @{ */
137void sample_json(); ///< JSON parsing and emitting
138/** @} */
139/** @defgroup doc_quickstart_style YAML styles
140 * @ingroup doc_quickstart
141 * @{ */
142void sample_style(); ///< query/set node styles
143void sample_style_flow_formatting();///< control formatting of flow containers
144void sample_style_flow_ml_indent(); ///< control indentation of FLOW_ML1 and FLOW_MLN containers
145/** @} */
146/** @defgroup doc_quickstart_anchors YAML anchors
147 * @ingroup doc_quickstart
148 * @{ */
149void sample_anchors_and_aliases(); ///< deal with YAML anchors and aliases
150void sample_anchors_and_aliases_create(); ///< how to create YAML anchors and aliases
151/** @} */
152/** @defgroup doc_quickstart_tags YAML tags
153 * @ingroup doc_quickstart
154 * @{ */
155void sample_tags(); ///< deal with YAML type tags
156void sample_tag_directives(); ///< deal with YAML tag namespace directives
157/** @} */
158/** @defgroup doc_quickstart_docs YAML documents
159 * @ingroup doc_quickstart
160 * @{ */
161void sample_docs(); ///< deal with YAML docs
162/** @} */
163/** @defgroup doc_quickstart_error Errors and error handlers
164 * @ingroup doc_quickstart
165 * @{ */
166void sample_error_handler(); ///< set custom error handlers
167void sample_error_basic(); ///< handler for basic errors, and obtain a full error message with basic context
168void sample_error_parse(); ///< handler for parse errors, and obtain a full error message with parse context
169void sample_error_visit(); ///< handler for visit errors, and obtain a full error message with visit context
170void sample_error_visit_location(); ///< obtaining the YAML location from a visit error
171/** @} */
172/** @defgroup doc_quickstart_allocators Allocators
173 * @ingroup doc_quickstart
174 * @{ */
175void sample_global_allocator(); ///< set a global allocator for ryml
176void sample_per_tree_allocator(); ///< set per-tree allocators
177/** @} */
178/** @defgroup doc_quickstart_static_trees Static trees
179 * @ingroup doc_quickstart
180 * @{ */
181void sample_static_trees(); ///< how to use static trees in ryml
182/** @} */
183
184
185
186//-----------------------------------------------------------------------------
187
188/** @defgroup doc_quickstart_helpers Sample helpers
189 *
190 * Helper utilities used in the quickstart.
191 * @ingroup doc_quickstart
192 */
193
194/** @cond dev */
195void handle_args(int, const char*[]);
196int report_checks();
197void ensure_callbacks();
198/** @endcond */
199
200// ryml can be used as a single header, or as a simple library:
201#if defined(RYML_SINGLE_HEADER) // using the single header directly in the executable
202 #define RYML_SINGLE_HDR_DEFINE_NOW
203 #include <ryml_all.hpp>
204#elif defined(RYML_SINGLE_HEADER_LIB) // using the single header from a library
205 #include <ryml_all.hpp>
206#else
207 // umbrella header bringing all of ryml. for compilation speed,
208 // prefer using individual includes.
209 #include <ryml.hpp>
210 // ryml itself does not use any STL container, so <ryml_std.hpp>
211 // is needed if interop with std containers is desired.
212 // This is an umbrella include bringing all the interop
213 // headers implemented in ryml; again, for compilation speed,
214 // prefer using individual includes.
215 #include <ryml_std.hpp> // optional header, provided for std:: interop
216 #include <c4/format.hpp> // needed for the examples below
217 #include <c4/format_base64.hpp> // needed for the examples below
218 // optional header to save/load files:
219 #include <c4/yml/file.hpp>
220 // optional header, definitions for error utilities to implement
221 // user-defined error callbacks:
222 #include <c4/yml/error.def.hpp>
223#endif
224
225
226// these are needed for the examples below
227#include <iostream>
228#include <sstream>
229#include <vector>
230#include <map>
231#ifdef C4_EXCEPTIONS
232#include <stdexcept>
233#else
234#include <csetjmp>
235#endif
236
237
238//-----------------------------------------------------------------------------
239
240int main(int argc, const char* argv[])
241{
242 handle_args(argc, argv);
274 sample_style();
277 sample_json();
280 sample_tags();
282 sample_docs();
291 return report_checks();
292}
293
294
295//-----------------------------------------------------------------------------
296//-----------------------------------------------------------------------------
297//-----------------------------------------------------------------------------
298// first, some helpers used in this quickstart
299
300/** @addtogroup doc_quickstart_helpers
301 *
302 * @{ */
303
304/** used by @ref CHECK() */
305bool report_check(int line, const char *predicate, bool result);
306
307// define the CHECK macro: a glorified assert()
308#ifdef __DOXYGEN__
309/// a testing assertion, used only in this quickstart
310# define CHECK(predicate) assert(predicate) // enable doxygen to link to the functions called inside CHECK()
311#else
312// GCC 4.8 has some problems, notably with raw string arguments
313# if (defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 4) || (__GNUC_MINOR__ > 8))))
314# define CHECK(predicate) report_check(__LINE__, #predicate, (predicate))
315# else
316# define CHECK CheckPredicate{__LINE__}
317 namespace {
318 struct CheckPredicate
319 {
320 int line;
321 void operator() (bool result) const { report_check(line, nullptr, result); }
322 };
323 } // namespace
324# endif // GCC 4.8
325#endif // doxygen
326
327
328
329/** an error handler used by some of the quickstart examples.
330 * @ingroup doc_quickstart_helpers */
332{
334 ryml::Callbacks original_callbacks; // saves the original callbacks
335public:
336 // utilities used below
337 template<class Fn> bool check_error_occurs(Fn &&fn);
338 template<class Fn> bool check_assertion_occurs(Fn &&fn);
339 void check_enabled() const;
340 void check_disabled() const;
342public:
343 // these are the functions that we want to execute on error
344 [[noreturn]] void on_error_basic(ryml::csubstr msg, ryml::ErrorDataBasic const& errdata);
345 [[noreturn]] void on_error_parse(ryml::csubstr msg, ryml::ErrorDataParse const& errdata);
346 [[noreturn]] void on_error_visit(ryml::csubstr msg, ryml::ErrorDataVisit const& errdata);
347public:
348 // these are the trampoline functions that we set ryml to call
349 [[noreturn]] static void s_error_basic(ryml::csubstr msg, ryml::ErrorDataBasic const& errdata, void *this_);
350 [[noreturn]] static void s_error_parse(ryml::csubstr msg, ryml::ErrorDataParse const& errdata, void *this_);
351 [[noreturn]] static void s_error_visit(ryml::csubstr msg, ryml::ErrorDataVisit const& errdata, void *this_);
352public:
353 // the handlers save these fields to be able to check on them
354 // after the error is triggered. They are only valid after an
355 // error, and before the next error.
356 std::string saved_msg_short;
357 std::string saved_msg_full;
363};
364
365
366/** Shows how to create a scoped error handler.
367 * @ingroup doc_quickstart_helpers */
381
382// needed to setup the callbacks when ryml does not provide them
383void ensure_callbacks();
385
386
387C4_SUPPRESS_WARNING_GCC_CLANG_PUSH
388C4_SUPPRESS_WARNING_GCC_CLANG("-Wold-style-cast")
389C4_SUPPRESS_WARNING_GCC_CLANG("-Wcast-qual")
390C4_SUPPRESS_WARNING_GCC("-Wuseless-cast")
391C4_SUPPRESS_WARNING_GCC("-Wuseless-cast")
392#if defined(__GNUC__) && (__GNUC__ >= 6)
393C4_SUPPRESS_WARNING_GCC("-Wnull-dereference") // false positives
394#endif
395#if defined(__clang__) && (__clang_major__ >= 13)
396C4_SUPPRESS_WARNING_CLANG("-Wreserved-identifier")
397#endif
398
399
400/** @} */ // doc_quickstart_helpers
401
402
403//-----------------------------------------------------------------------------
404//-----------------------------------------------------------------------------
405//-----------------------------------------------------------------------------
406
407/** a lightning tour over most features
408 * see @ref sample_quick_overview */
410{
411 // Parse YAML code in place, potentially mutating the buffer:
412 char yml_buf[] = "{foo: 1, bar: [2, 3], john: doe}";
413 ryml::Tree tree = ryml::parse_in_place(yml_buf);
414
415 // read from the tree:
416 ryml::NodeRef bar = tree["bar"];
417 CHECK(bar[0].val() == "2");
418 CHECK(bar[1].val() == "3");
419 CHECK(bar[0].val().str == yml_buf + 15); // points at the source buffer
420 CHECK(bar[1].val().str == yml_buf + 18);
421
422 // deserializing:
423 int bar0 = 0, bar1 = 0;
424 bar[0].load(&bar0); // also checks the node is readable, and conversion succeeded
425 bar[1].load(&bar1); // see also .deserialize()
426 CHECK(bar0 == 2);
427 CHECK(bar1 == 3);
428
429 // serializing:
430 bar[0].save(10); // creates a string in the tree's arena
431 bar[1].save(11); // see also .set_serialized()
432 CHECK(bar[0].val() == "10");
433 CHECK(bar[1].val() == "11");
434
435 // add nodes
436 tree["new"].set_val("node");
437 bar.append_child().save(12);
438 CHECK(bar[2].val() == "12");
439
440 // emit tree
441 std::string expected = "{foo: 1,bar: [10,11,12],john: doe,new: node}";
442 // emit tree to std::string
443 CHECK(ryml::emitrs_yaml<std::string>(tree) == expected);
444 // emit tree to FILE*
445 ryml::emit_yaml(tree, stdout); printf("\n");
446 // emit tree to ostream
447 std::cout << tree << "\n";
448
449 // emit node
450 ryml::ConstNodeRef foo = tree["foo"];
451 expected = "foo: 1\n";
452 // emit node to std::string
453 CHECK(ryml::emitrs_yaml<std::string>(foo) == expected);
454 // emit node to FILE*
455 ryml::emit_yaml(foo, stdout);
456 // emit node to ostream
457 std::cout << foo;
458}
459
460
461//-----------------------------------------------------------------------------
462
463/** a brief tour over most features */
465{
466 // Parse YAML code in place, potentially mutating the buffer:
467 char yml_buf[] = ""
468 "foo: 1" "\n"
469 "bar: [2, 3]" "\n"
470 "john: doe" "\n"
471 "";
472 ryml::Tree tree = ryml::parse_in_place(yml_buf);
473
474 // The resulting tree contains only views to the parsed string. If
475 // the string was parsed in place, then the string must outlive
476 // the tree! This works here because both `yml_buf` and `tree`
477 // live on the same scope, so have the same lifetime.
478
479 // It is also possible to:
480 //
481 // - parse a read-only buffer using parse_in_arena(). This
482 // copies the YAML buffer to the tree's arena, and spares the
483 // headache of the string's lifetime.
484 //
485 // - reuse an existing tree (advised)
486 //
487 // - reuse an existing parser (advised)
488 //
489 // - parse into an existing node deep in a tree
490 //
491 // Note: it will always be significantly faster to parse in place
492 // and reuse tree+parser.
493 //
494 // Below you will find samples that show how to achieve reuse; but
495 // please note that for brevity and clarity, many of the examples
496 // here are parsing in the arena, and not reusing tree or parser.
497
498
499 //------------------------------------------------------------------
500 // API overview
501
502 // The ryml tree has a two-level API:
503 //
504 // The lower level index API is based on the indices of nodes,
505 // where the node's id is the node's position in the tree's data
506 // array. This API is very efficient, but somewhat difficult to use:
507 ryml::id_type root_id = tree.root_id();
508 ryml::id_type bar_id = tree.find_child(root_id, "bar"); // need to get the index right
509 CHECK(tree.is_map(root_id)); // all of the index methods are in the tree
510 CHECK(tree.is_seq(bar_id)); // ... and receive the subject index
511
512 // The node API is a lightweight abstraction sitting on top of the
513 // index API, but offering a much more convenient interaction:
514 ryml::ConstNodeRef root = tree.rootref(); // a const node reference
515 ryml::ConstNodeRef bar = tree["bar"];
516 CHECK(root.is_map());
517 CHECK(bar.is_seq());
518
519 // A node ref is a lightweight handle to the tree and associated id:
520 CHECK(root.tree() == &tree); // a node ref points at its tree, WITHOUT refcount
521 CHECK(root.id() == root_id); // a node ref's id is the index of the node
522 CHECK(bar.id() == bar_id); // a node ref's id is the index of the node
523
524 // The node API translates very cleanly to the index API, so most
525 // of the code examples below are using the node API.
526
527 // WARNING. A node ref holds a raw pointer to the tree. Care must
528 // be taken to ensure the lifetimes match, so that a node will
529 // never access the tree after the tree goes out of scope.
530
531
532 //------------------------------------------------------------------
533 // To read the parsed tree
534
535 // ConstNodeRef::operator[] does a lookup, is O(num_children[node]).
536 CHECK(tree["foo"].is_keyval());
537 CHECK(tree["foo"].val() == "1"); // get the val of a node (must be leaf node, otherwise it is a container and has no val)
538 CHECK(tree["foo"].key() == "foo"); // get the key of a node (must be child of a map, otherwise it has no key)
539 CHECK(tree["bar"].is_seq());
540 CHECK(tree["bar"].has_key());
541 CHECK(tree["bar"].key() == "bar");
542 // maps use string keys, seqs use index keys:
543 CHECK(tree["bar"][0].val() == "2");
544 CHECK(tree["bar"][1].val() == "3");
545 CHECK(tree["john"].val() == "doe");
546 // An index key is the position of the child within its parent,
547 // so even maps can also use int keys, if the key position is
548 // known.
549 CHECK(tree[0].id() == tree["foo"].id());
550 CHECK(tree[1].id() == tree["bar"].id());
551 CHECK(tree[2].id() == tree["john"].id());
552 // Tree::operator[](int) searches a ***root*** child by its position.
553 CHECK(tree[0].id() == tree["foo"].id()); // 0: first child of root
554 CHECK(tree[1].id() == tree["bar"].id()); // 1: second child of root
555 CHECK(tree[2].id() == tree["john"].id()); // 2: third child of root
556 // NodeRef::operator[](int) searches a ***node*** child by its position:
557 CHECK(bar[0].val() == "2"); // 0 means first child of bar
558 CHECK(bar[1].val() == "3"); // 1 means second child of bar
559 // NodeRef::operator[](string):
560 // A string key is the key of the node: lookup is by name. So it
561 // is only available for maps, and it is NOT available for seqs,
562 // since seq members do not have keys.
563 CHECK(tree["foo"].key() == "foo");
564 CHECK(tree["bar"].key() == "bar");
565 CHECK(tree["john"].key() == "john");
566 CHECK(bar.is_seq());
567 // CHECK(bar["BOOM!"].is_seed()); // error, seqs do not have key lookup
568
569 // Note that maps can also use index keys as well as string keys:
570 CHECK(root["foo"].id() == root[0].id());
571 CHECK(root["bar"].id() == root[1].id());
572 CHECK(root["john"].id() == root[2].id());
573
574 // IMPORTANT. The ryml tree uses an index-based linked list for
575 // storing children, so the complexity of
576 // `Tree::operator[csubstr]` and `Tree::operator[id_type]` is O(n),
577 // linear on the number of root children. If you use
578 // `Tree::operator[]` with a large tree where the root has many
579 // children, you will see a performance hit.
580 //
581 // To avoid this hit, you can create your own accelerator
582 // structure. For example, before doing a lookup, do a single
583 // traverse at the root level to fill an `map<csubstr,id_type>`
584 // mapping key names to node indices; with a node index, a lookup
585 // (via `Tree::get()`) is O(1), so this way you can get O(log n)
586 // lookup from a key. (But please do not use `std::map` if you
587 // care about performance; use something else like a flat map or
588 // sorted vector).
589 //
590 // As for node refs, the difference from `NodeRef::operator[]` and
591 // `ConstNodeRef::operator[]` to `Tree::operator[]` is that the
592 // latter refers to the root node, whereas the former are invoked
593 // on their target node. But the lookup process works the same for
594 // both and their algorithmic complexity is the same: they are
595 // both linear in the number of direct children. But of course,
596 // depending on the data, that number may be very different from
597 // one to another.
598
599
600 //------------------------------------------------------------------
601 // Hierarchy:
602
603 {
604 ryml::ConstNodeRef foo = root.first_child();
605 ryml::ConstNodeRef john = root.last_child();
606 CHECK(tree.size() == 6); // O(1) number of nodes in the tree
607 CHECK(root.num_children() == 3); // O(num_children[root])
608 CHECK(foo.num_siblings() == 3); // O(num_children[parent(foo)])
609 CHECK(foo.parent().id() == root.id()); // parent() is O(1)
610 CHECK(root.first_child().id() == root["foo"].id()); // first_child() is O(1)
611 CHECK(root.last_child().id() == root["john"].id()); // last_child() is O(1)
612 CHECK(john.first_sibling().id() == foo.id());
613 CHECK(foo.last_sibling().id() == john.id());
614 // prev_sibling(), next_sibling(): (both are O(1))
615 CHECK(foo.num_siblings() == root.num_children());
616 CHECK(foo.prev_sibling().id() == ryml::NONE); // foo is the first_child()
617 CHECK(foo.next_sibling().key() == "bar");
618 CHECK(foo.next_sibling().next_sibling().key() == "john");
619 CHECK(foo.next_sibling().next_sibling().next_sibling().id() == ryml::NONE); // john is the last_child()
620 }
621
622
623 //------------------------------------------------------------------
624 // Iterating:
625 {
626 ryml::csubstr expected_keys[] = {"foo", "bar", "john"};
627 // iterate children using the high-level node API:
628 {
629 ryml::id_type count = 0;
630 for(ryml::ConstNodeRef const& child : root.children())
631 CHECK(child.key() == expected_keys[count++]);
632 }
633 // iterate siblings using the high-level node API:
634 {
635 ryml::id_type count = 0;
636 for(ryml::ConstNodeRef const& child : root["foo"].siblings())
637 CHECK(child.key() == expected_keys[count++]);
638 }
639 // iterate children using the lower-level tree index API:
640 {
641 ryml::id_type count = 0;
642 for(ryml::id_type child_id = tree.first_child(root_id); child_id != ryml::NONE; child_id = tree.next_sibling(child_id))
643 CHECK(tree.key(child_id) == expected_keys[count++]);
644 }
645 // iterate siblings using the lower-level tree index API:
646 // (notice the only difference from above is in the loop
647 // preamble, which calls tree.first_sibling(bar_id) instead of
648 // tree.first_child(root_id))
649 {
650 ryml::id_type count = 0;
651 for(ryml::id_type child_id = tree.first_sibling(bar_id); child_id != ryml::NONE; child_id = tree.next_sibling(child_id))
652 CHECK(tree.key(child_id) == expected_keys[count++]);
653 }
654 }
655
656
657 //------------------------------------------------------------------
658 // Gotchas:
659
660 // ryml uses assertions to prevent you from trying to obtain
661 // things that do not exist. For example:
662
663 {
664 ryml::ConstNodeRef seq_node = tree["bar"];
665 ryml::ConstNodeRef val_node = seq_node[0];
666
667 CHECK(seq_node.is_seq()); // seq is a container
668 CHECK(!seq_node.has_val()); // ... so it has no val
669 //CHECK(seq_node.val() == BOOM!); // ... so attempting to get a val is undefined behavior
670
671 CHECK(val_node.parent() == seq_node); // belongs to a seq
672 CHECK(!val_node.has_key()); // ... so it has no key
673 //CHECK(val_node.key() == BOOM!); // ... so attempting to get a key is undefined behavior
674
675 CHECK(val_node.is_val()); // this node is a val
676 //CHECK(val_node.first_child() == BOOM!); // ... so attempting to get a child is undefined behavior
677
678 // assertions are also present in methods that /may/ read the val:
679 CHECK(seq_node.is_seq()); // seq is a container
680 //CHECK(seq_node.val_is_null() BOOM!); // so cannot get the val to check
681 }
682
683 // By default, assertions are enabled unless the NDEBUG macro is
684 // defined (which happens in release builds).
685 //
686 // This adheres to the pay-only-for-what-you-use philosophy: if
687 // you are sure that your intent is correct, why would you need to
688 // pay the runtime cost for the assertions?
689 //
690 // The downside, of course, is that you may be unsure, or your
691 // code may be wrong; and then release builds may end up doing
692 // something wrong.
693 //
694 // So in that case, you can use the appropriate ryml predicates to
695 // check your intent (as in the examples above), or you can
696 // explicitly enable/disable assertions, by defining the macro
697 // RYML_USE_ASSERT to a proper value (see c4/yml/common.hpp). This
698 // will make problematic code trigger a call to the appropriate
699 // error callback.
700 //
701 // Also, to be clear, this does not apply to parse errors
702 // occurring when the YAML is parsed. Parse errors are always
703 // detected by the parser: this is not done through assertions,
704 // but through the appropriate error checking mechanism. So
705 // checking for these errors is always enabled and cannot be
706 // switched off. As for deserialization errors, see below.
707
708
709 //------------------------------------------------------------------
710 // Deserializing:
711 //
712 // - use .load(T*)/.load_key(T*) for lazy code with checked reads
713 // - ryml checks preconditions (node readability, etc), or triggers error
714 // - ryml checks deserialization, or triggers error
715 // - more expensive because of all the checks and error marshalling
716 // - optionally, can disable precondition checks with bool parameter
717 //
718 // - use .deserialize()/.deserialize_key() for sure reads
719 // - ryml asserts preconditions (readability, etc) (err on debug builds, UB otherwise)
720 // - ryml returns bool for deserialization status, [[nodiscard]]
721 // - cheaper but potentially dangerous
722 //
723 {
724 // fundamental types: ryml provides deserialization facilities
725 // .load() checks for readability + conversion errors
726 {
727 int foo = 0, bar0 = 0, bar1 = 0;
728 root["foo"].load(&foo); // calls err_visit() on failure
729 root["bar"][0].load(&bar0);
730 root["bar"][1].load(&bar1);
731 CHECK(foo == 1);
732 CHECK(bar0 == 2);
733 CHECK(bar1 == 3);
734 // .load() can also be called from the tree
735 }
736 // .deserialize() asserts readability, user checks conversion errors
737 {
738 int foo = 0, bar0 = 0, bar1 = 0;
739 CHECK(root["foo"].deserialize(&foo)); // user must check return value
740 CHECK(root["bar"][0].deserialize(&bar0));
741 CHECK(root["bar"][1].deserialize(&bar1));
742 CHECK(foo == 1);
743 CHECK(bar0 == 2);
744 CHECK(bar1 == 3);
745 // .deserialize() can also be called from the tree
746 }
747 // std containers: ryml optionally provides deserialization
748 // facilities. see serialization samples below.
749 {
750 std::string john_str, bar_str;
751 root["john"].load(&john_str);
752 root["bar"].load_key(&bar_str);
753 CHECK(john_str == "doe");
754 CHECK(bar_str == "bar");
755 std::vector<int> bar_actual, bar_expected{{2, 3}};
756 root["bar"].load(&bar_actual);
757 CHECK(bar_actual == bar_expected);
758 }
759 }
760
761
762 //------------------------------------------------------------------
763 // Modifying existing nodes: assigning vs serializing
764
765 // As implied by its name, ConstNodeRef is a reference to a const
766 // node. It can be used to read from the node, but not write to it
767 // or modify the hierarchy of the node. If any modification is
768 // desired then a NodeRef must be used instead:
769 ryml::NodeRef wroot = tree.rootref(); // writeable root
770
771 // .set_val() assigns an existing string to the receiving node.
772 // The contents are NOT copied, and the string pointer will be in
773 // effect until the tree goes out of scope! So BEWARE to only
774 // assign from strings outliving the tree.
775 wroot["foo"].set_val("says you");
776 wroot["bar"][0].set_val("-2");
777 wroot["bar"][1].set_val("-3");
778 wroot["john"].set_val("ron");
779 // Now the tree is _pointing_ at the memory of the strings above.
780 // In this case it is OK because those are static strings, located
781 // in the executable's static section, and will outlive the tree.
782 CHECK(root["foo"].val() == "says you");
783 CHECK(root["bar"][0].val() == "-2");
784 CHECK(root["bar"][1].val() == "-3");
785 CHECK(root["john"].val() == "ron");
786 // But WATCHOUT: do not assign from temporary objects:
787 // {
788 // std::string bad("will dangle");
789 // root["john"] = bad;
790 // }
791 // CHECK(root["john"] == "dangling"); // CRASH! the string was deallocated
792
793 // For non-string types, or for strings whose lifetime is
794 // problematic WRT the tree, you can create and save a string in
795 // the tree using .set_serialized() (or its equivalent .save() if
796 // you want to have ryml do the error checking). It first
797 // serializes values to a string arena owned by the tree, then
798 // assigns the serialized string to the receiving node. This
799 // avoids constraints with the lifetime, since the arena lives
800 // with the tree.
801 CHECK(tree.arena().empty());
802 wroot["foo"].set_serialized("says who"); // requires to_chars(). see serialization samples below.
803 wroot["bar"][0].set_serialized(20);
804 wroot["bar"][1].set_serialized(30);
805 wroot["john"].set_serialized("deere");
806 CHECK(root["foo"].val() == "says who");
807 CHECK(root["bar"][0].val() == "20");
808 CHECK(root["bar"][1].val() == "30");
809 CHECK(root["john"].val() == "deere");
810 CHECK(tree.arena() == "says who2030deere"); // the result of serializations to the tree arena
811 // with .set_serialize()/.save(), the crash above is avoided:
812 {
813 std::string ok("in_scope");
814 // root["john"].set_val(ok); // don't, will dangle
815 wroot["john"].set_serialized(ok); // OK, copy to the tree's arena
816 }
817 CHECK(root["john"].val() == "in_scope"); // OK! val is now in the tree's arena
818 // serializing floating points:
819 wroot["float"].set_serialized(2.4f);
820 // to force a particular precision or float format:
821 // (see sample_float_precision() and sample_formatting())
822 wroot["digits"].set_serialized(ryml::fmt::real(2.4, /*num_digits*/6, ryml::FTOA_FLOAT));
823 CHECK(tree.arena() == "says who2030deerein_scope2.42.400000"); // the result of serializations to the tree arena
824
825
826 //------------------------------------------------------------------
827 // Adding new nodes:
828
829 // adding a keyval node to a map:
830 CHECK(root.num_children() == 5);
831 wroot["newkeyval"].set_val("shiny and new"); // using these strings
832 c4::yml::NodeRef chsrl = wroot.append_child();
833 chsrl.set_key_serialized("newkeyval (serialized)"); // serializes and assigns the serialization
834 chsrl.set_serialized("shiny and new (serialized)"); // serializes and assigns the serialization
835 CHECK(root.num_children() == 7);
836 CHECK(root["newkeyval"].key() == "newkeyval");
837 CHECK(root["newkeyval"].val() == "shiny and new");
838 CHECK(root["newkeyval (serialized)"].key() == "newkeyval (serialized)");
839 CHECK(root["newkeyval (serialized)"].val() == "shiny and new (serialized)");
840 CHECK( ! tree.in_arena(root["newkeyval"].key())); // it's using directly the static string above
841 CHECK( ! tree.in_arena(root["newkeyval"].val())); // it's using directly the static string above
842 CHECK( tree.in_arena(root["newkeyval (serialized)"].key())); // it's using a serialization of the string above
843 CHECK( tree.in_arena(root["newkeyval (serialized)"].val())); // it's using a serialization of the string above
844 // adding a val node to a seq:
845 CHECK(root["bar"].num_children() == 2);
846 wroot["bar"][2].set_val("oh so nice");
847 wroot["bar"][3].set_serialized("oh so nice (serialized)");
848 CHECK(root["bar"].num_children() == 4);
849 CHECK(root["bar"][2].val() == "oh so nice");
850 CHECK(root["bar"][3].val() == "oh so nice (serialized)");
851 // adding a seq node:
852 CHECK(root.num_children() == 7);
853 wroot["newseq"].set_seq();
854 chsrl = wroot.append_child();
855 chsrl.set_key_serialized("newseq (serialized)");
856 chsrl.set_seq();
857 CHECK(root.num_children() == 9);
858 CHECK(root["newseq"].num_children() == 0);
859 CHECK(root["newseq"].is_seq());
860 CHECK(root["newseq (serialized)"].num_children() == 0);
861 CHECK(root["newseq (serialized)"].is_seq());
862 // adding a map node:
863 CHECK(root.num_children() == 9);
864 wroot["newmap"].set_map();
865 chsrl = wroot.append_child();
866 chsrl.set_key_serialized("newmap (serialized)");
867 chsrl.set_map();
868 CHECK(root.num_children() == 11);
869 CHECK(root["newmap"].num_children() == 0);
870 CHECK(root["newmap"].is_map());
871 CHECK(root["newmap (serialized)"].num_children() == 0);
872 CHECK(root["newmap (serialized)"].is_map());
873 //
874 // When the tree is const, operator[] searches the tree for a
875 // matching key/position, and returns it, or raises an error if
876 // none is found.
877 //
878 // When the tree is mutable, operator[] will not mutate the tree
879 // until the returned node is written to. operator[] first
880 // searches the tree for a node matching the key/position and
881 // returns it if it exists; otherwise, the result is a NodeRef
882 // object which keeps in itself the required information to write
883 // to the proper place in the tree. This is called being in a
884 // "seed" state.
885 //
886 // This means that passing a key/position which does not exist
887 // will not mutate the tree, but will instead store (in the
888 // returned NodeRef) the proper place of the tree to be able to do
889 // so, if and when it is required. This is why the node is said to
890 // be in "seed" state - it allows creating the entry in the tree
891 // in the future.
892 //
893 // This is a significant difference from eg, the behavior of
894 // std::map, which mutates the map immediately within the call to
895 // operator[].
896 //
897 // All of the points above apply only if the tree is mutable. If
898 // the tree is const, then a NodeRef cannot be obtained from it;
899 // only a ConstNodeRef, which can never be used to mutate the
900 // tree.
901 //
902 CHECK(!root.has_child("I am not nothing"));
903 ryml::NodeRef nothing;
904 CHECK(nothing.invalid()); // invalid because it points at nothing
905 nothing = wroot["I am nothing"];
906 CHECK(!nothing.invalid()); // points at the tree, and a specific place in the tree
907 CHECK(nothing.is_seed()); // ... but nothing is there yet.
908 CHECK(!root.has_child("I am nothing")); // same as above
909 CHECK(!nothing.readable()); // ... and this node cannot be used to
910 // read anything from the tree
911 ryml::NodeRef something = wroot["I am something"];
912 ryml::ConstNodeRef constsomething = wroot["I am something"];
913 CHECK(!root.has_child("I am something")); // same as above
914 CHECK(!something.invalid());
915 CHECK(something.is_seed()); // same as above
916 CHECK(!something.readable()); // same as above
917 CHECK(constsomething.invalid()); // NOTE: because a ConstNodeRef cannot be
918 // used to mutate a tree, it is only valid()
919 // if it is pointing at an existing node.
920 something.set_val("indeed"); // this will commit the seed to the tree, mutating at the proper place
921 CHECK(root.has_child("I am something"));
922 CHECK(root["I am something"].val() == "indeed");
923 CHECK(!something.invalid()); // it was already valid
924 CHECK(!something.is_seed()); // now the tree has this node, so the
925 // ref is no longer a seed
926 CHECK(something.readable()); // and it is now readable
927 //
928 // now the constref is also valid (but it needs to be reassigned):
929 ryml::ConstNodeRef constsomethingnew = wroot["I am something"];
930 CHECK(!constsomethingnew.invalid());
931 CHECK(constsomethingnew.readable());
932 // note that the old constref is now stale, because it only keeps
933 // the state at creation:
934 CHECK(constsomething.invalid());
935 CHECK(!constsomething.readable());
936 //
937 // -----------------------------------------------------------
938 // Remember: a seed node cannot be used to read from the tree!
939 // -----------------------------------------------------------
940 //
941 // The seed node needs to be created and become readable first.
942 //
943 // Trying to invoke any tree-reading method on a node that is not
944 // readable will cause an assertion (see RYML_USE_ASSERT).
945 //
946 // It is your responsibility to verify that the preconditions are
947 // met. If you are not sure about the structure of your data,
948 // write your code defensively to signify your full intent:
949 //
950 ryml::NodeRef wbar = wroot["bar"];
951 if(wbar.readable() && wbar.is_seq()) // .is_seq() requires .readable()
952 {
953 CHECK(wbar[0].readable() && wbar[0].val() == "20");
954 CHECK( ! wbar[100].readable());
955 CHECK( ! wbar[100].readable() || wbar[100].val() == "100"); // <- no crash because it is not .readable(), so never tries to call .val()
956 // this would work as well:
957 CHECK( ! wbar[0].is_seed() && wbar[0].val() == "20");
958 CHECK(wbar[100].is_seed() || wbar[100].val() == "100");
959 }
960
961
962 //------------------------------------------------------------------
963 // .operator[]() vs .at()
964
965 // (Const)NodeRef::operator[] is an analogue to std::vector::operator[].
966 // (Const)NodeRef::at() is an analogue to std::vector::at()
967 //
968 // at() will always check the subject node is .readable().
969 //
970 // [] is meant for the happy path: unverified in Release builds,
971 // and asserts .readable() in Debug builds (more specifically when
972 // RYML_USE_ASSERT is defined and NDEBUG is not defined).
973 {
974 // in this example we will be checking errors, so set up a
975 // temporary error handler to catch them:
976 ScopedErrorHandlerExample errh; // calls ryml::set_callbacks()
977 // instantiate the tree after errh
978 ryml::Tree err_tree = ryml::parse_in_arena("{foo: bar}");
979 // ... so that the tree uses the current callbacks:
980 CHECK(err_tree.callbacks() == errh.callbacks());
981 // node does not exist, only a seed node
982 ryml::NodeRef seed_node = err_tree["this"];
983 // ... therefore not .readable()
984 CHECK(!seed_node.readable());
985 // using .at() reliably produces an error:
986 CHECK(errh.check_error_occurs([&]{
987 return seed_node.at("is").at("an").at("invalid").at("operation");
988 // ^
989 // error occurs here because it is unreadable
990 }));
991 // ... but using [] fails only when RYML_USE_ASSERT is
992 // defined. otherwise, it's the dreaded Undefined Behavior:
994 return seed_node["is"]["an"]["invalid"]["operation"];
995 // ^
996 // assertion occurs here because it is unreadable
997 }));
998 }
999
1000
1001 //------------------------------------------------------------------
1002 // Emitting:
1003
1004 ryml::csubstr expected_result =
1005 "foo: says who" "\n"
1006 "bar: [20,30,oh so nice,oh so nice (serialized)]" "\n"
1007 "john: in_scope" "\n"
1008 "float: 2.4" "\n"
1009 "digits: 2.400000" "\n"
1010 "newkeyval: shiny and new" "\n"
1011 "newkeyval (serialized): shiny and new (serialized)" "\n"
1012 "newseq: []" "\n"
1013 "newseq (serialized): []" "\n"
1014 "newmap: {}" "\n"
1015 "newmap (serialized): {}" "\n"
1016 "I am something: indeed" "\n"
1017 "";
1018
1019 // emit to a FILE*
1020 ryml::emit_yaml(tree, stdout);
1021 // emit to a stream
1022 // (this is templated on the stream, so it works both with
1023 // standard streams like std::ostream/std::stringstream, or with
1024 // user-defined streams not inheriting from ostream)
1025 std::stringstream ss;
1026 ss << tree;
1027 std::string stream_result = ss.str();
1028 // emit to a buffer:
1029 std::string str_result = ryml::emitrs_yaml<std::string>(tree);
1030 // can emit to any given buffer:
1031 char buf[1024];
1032 ryml::csubstr buf_result = ryml::emit_yaml(tree, buf);
1033 // now check
1034 CHECK(buf_result == expected_result);
1035 CHECK(str_result == expected_result);
1036 CHECK(stream_result == expected_result);
1037 // There are many possibilities to emit to buffer;
1038 // please look at the emit sample functions below.
1039
1040
1041 //------------------------------------------------------------------
1042 // ConstNodeRef vs NodeRef
1043
1044 ryml::NodeRef noderef = tree["bar"][0];
1045 ryml::ConstNodeRef constnoderef = tree["bar"][0];
1046
1047 // ConstNodeRef cannot be used to mutate the tree:
1048 //constnoderef.set_val("21"); // compile error: method does not exist
1049 //constnoderef.set_serialized("22"); // compile error
1050 // ... but a NodeRef can:
1051 noderef.set_val("21"); // ok, can assign because it's not const
1052 CHECK(tree["bar"][0].val() == "21");
1053 noderef.set_serialized("22"); // ok, can serialize and assign because it's not const
1054 CHECK(tree["bar"][0].val() == "22");
1055
1056 // it is not possible to obtain a NodeRef from a ConstNodeRef:
1057 // noderef = constnoderef; // compile error
1058
1059 // it is always possible to obtain a ConstNodeRef from a NodeRef:
1060 constnoderef = noderef; // ok can assign const <- nonconst
1061
1062 // If a tree is const, then only ConstNodeRef's can be
1063 // obtained from that tree:
1064 ryml::Tree const& consttree = tree;
1065 //noderef = consttree["bar"][0]; // compile error
1066 noderef = tree["bar"][0]; // ok
1067 constnoderef = consttree["bar"][0]; // ok
1068
1069 // ConstNodeRef and NodeRef can be compared for equality.
1070 // Equality means they point at the same node.
1071 CHECK(constnoderef == noderef);
1072 CHECK(!(constnoderef != noderef));
1073
1074
1075 //------------------------------------------------------------------
1076 // Getting the location of nodes in the source:
1077 //
1078 // Location tracking is opt-in:
1079 ryml::EventHandlerTree evt_handler = {};
1080 ryml::Parser parser(&evt_handler, ryml::ParserOptions().locations(true));
1081 // Now the parser will start by building the accelerator structure:
1082 ryml::Tree tree2 = parse_in_arena(&parser, "expected.yml", expected_result);
1083 // ... and use it when querying
1084 ryml::ConstNodeRef subject_node = tree2["bar"][1];
1085 CHECK(subject_node.val() == "30");
1086 ryml::Location loc = subject_node.location(parser);
1087 CHECK(parser.location_contents(loc).begins_with("30"));
1088 CHECK(loc.line == 1u);
1089 CHECK(loc.col == 9u);
1090 // For further details in location tracking,
1091 // refer to the sample function below.
1092
1093
1094 //------------------------------------------------------------------
1095 // Dealing with UTF8
1097 "#" "\n"
1098 "# UTF8/16/32 can be placed directly in any scalar:" "\n"
1099 "#" "\n"
1100 "en: Planet (Gas)" "\n"
1101 "fr: Planète (Gazeuse)" "\n"
1102 "ru: Планета (Газ)" "\n"
1103 "ja: 惑星(ガス)" "\n"
1104 "zh: 行星(气体)" "\n"
1105 "#" "\n"
1106 "# UTF8 decoding only happens in double-quoted strings," "\n"
1107 "# as per the YAML standard" "\n"
1108 "#" "\n"
1109 "decode this: \"\\u263A c\\x61f\\xE9\"" "\n"
1110 "and this as well: \"\\u2705 \\U0001D11E\"" "\n"
1111 "not decoded: '\\u263A \\xE2\\x98\\xBA'" "\n"
1112 "neither this: '\\u2705 \\U0001D11E'" "\n");
1113 // in-place UTF8 just works:
1114 CHECK(langs["en"].val() == "Planet (Gas)");
1115 CHECK(langs["fr"].val() == "Planète (Gazeuse)");
1116 CHECK(langs["ru"].val() == "Планета (Газ)");
1117 CHECK(langs["ja"].val() == "惑星(ガス)");
1118 CHECK(langs["zh"].val() == "行星(气体)");
1119 // and \x \u \U codepoints are decoded, but only when they appear
1120 // inside double-quoted strings, as dictated by the YAML
1121 // standard:
1122 CHECK(langs["decode this"].val() == "☺ café");
1123 CHECK(langs["and this as well"].val() == "✅ 𝄞");
1124 CHECK(langs["not decoded"].val() == "\\u263A \\xE2\\x98\\xBA");
1125 CHECK(langs["neither this"].val() == "\\u2705 \\U0001D11E");
1126}
1127
1128
1129//-----------------------------------------------------------------------------
1130
1131/** demonstrate usage of ryml::substr and ryml::csubstr
1132 *
1133 * These types are imported from the c4core library into the ryml
1134 * namespace You may have noticed above the use of a `csubstr`
1135 * class. This class is defined in another library,
1136 * [c4core](https://github.com/biojppm/c4core), which is imported by
1137 * ryml. This is a library I use with my projects consisting of
1138 * multiplatform low-level utilities. One of these is `c4::csubstr`
1139 * (the name comes from "constant substring") which is a non-owning
1140 * read-only string view, with many methods that make it practical to
1141 * use (I would certainly argue more practical than `std::string`). In
1142 * fact, `c4::csubstr` and its writeable counterpart `c4::substr` are
1143 * the workhorses of the ryml parsing and serialization code.
1144 *
1145 * @see doc_substr */
1147{
1148 // substr is a mutable view: pointer and length to a string in memory.
1149 // csubstr is a const-substr (immutable).
1150
1151 // construct from explicit args
1152 {
1153 const char foobar_str[] = "foobar";
1154 auto s = ryml::csubstr(foobar_str, strlen(foobar_str));
1155 CHECK(s == "foobar");
1156 CHECK(s.size() == 6);
1157 CHECK(s.data() == foobar_str);
1158 CHECK(s.size() == s.len);
1159 CHECK(s.data() == s.str);
1160 }
1161
1162 // construct from a string array
1163 {
1164 const char foobar_str[] = "foobar";
1165 ryml::csubstr s = foobar_str;
1166 CHECK(s == "foobar");
1167 CHECK(s != "foobar0");
1168 CHECK(s.size() == 6); // does not include the terminating \0
1169 CHECK(s.data() == foobar_str);
1170 CHECK(s.size() == s.len);
1171 CHECK(s.data() == s.str);
1172 }
1173 // you can also declare directly in-place from an array:
1174 {
1175 ryml::csubstr s = "foobar";
1176 CHECK(s == "foobar");
1177 CHECK(s != "foobar0");
1178 CHECK(s.size() == 6);
1179 CHECK(s.size() == s.len);
1180 CHECK(s.data() == s.str);
1181 }
1182
1183 // construct from a C-string:
1184 //
1185 // Since the input is only a pointer, the string length can only
1186 // be found with a call to strlen(). To make this cost evident, we
1187 // require calling to_csubstr():
1188 {
1189 const char *foobar_str = "foobar";
1190 ryml::csubstr s = ryml::to_csubstr(foobar_str);
1191 CHECK(s == "foobar");
1192 CHECK(s != "foobar0");
1193 CHECK(s.size() == 6);
1194 CHECK(s.size() == s.len);
1195 CHECK(s.data() == s.str);
1196 }
1197
1198 // construct from a std::string: same approach as above.
1199 // requires inclusion of the <ryml/std/string.hpp> header
1200 // or of the umbrella header <ryml_std.hpp>.
1201 //
1202 // not including <string> in the default header is a deliberate
1203 // design choice to avoid including the heavy std:: allocation
1204 // machinery
1205 {
1206 std::string foobar_str = "foobar";
1207 ryml::csubstr s = ryml::to_csubstr(foobar_str); // defined in <ryml/std/string.hpp>
1208 CHECK(s == "foobar");
1209 CHECK(s != "foobar0");
1210 CHECK(s.size() == 6);
1211 CHECK(s.size() == s.len);
1212 CHECK(s.data() == s.str);
1213 }
1214
1215 // convert substr -> csubstr
1216 {
1217 char buf[] = "foo";
1218 ryml::substr foo = buf;
1219 CHECK(foo.len == 3);
1220 CHECK(foo.data() == buf);
1221 ryml::csubstr cfoo = foo;
1222 CHECK(cfoo.data() == buf);
1223 }
1224 // cannot convert csubstr -> substr:
1225 {
1226 // ryml::substr foo2 = cfoo; // compile error: cannot write to csubstr
1227 }
1228
1229 // construct from char[]/const char[]: mutable vs immutable memory
1230 {
1231 char const foobar_str_ro[] = "foobar"; // ro := read-only
1232 char foobar_str_rw[] = "foobar"; // rw := read-write
1233 static_assert(std::is_array<decltype(foobar_str_ro)>::value, "this is an array");
1234 static_assert(std::is_array<decltype(foobar_str_rw)>::value, "this is an array");
1235 // csubstr <- read-only memory
1236 {
1237 ryml::csubstr foobar = foobar_str_ro;
1238 CHECK(foobar.data() == foobar_str_ro);
1239 CHECK(foobar.size() == strlen(foobar_str_ro));
1240 CHECK(foobar == "foobar"); // AKA strcmp
1241 }
1242 // csubstr <- read-write memory: you can create an immutable csubstr from mutable memory
1243 {
1244 ryml::csubstr foobar = foobar_str_rw;
1245 CHECK(foobar.data() == foobar_str_rw);
1246 CHECK(foobar.size() == strlen(foobar_str_rw));
1247 CHECK(foobar == "foobar"); // AKA strcmp
1248 }
1249 // substr <- read-write memory.
1250 {
1251 ryml::substr foobar = foobar_str_rw;
1252 CHECK(foobar.data() == foobar_str_rw);
1253 CHECK(foobar.size() == strlen(foobar_str_rw));
1254 CHECK(foobar == "foobar"); // AKA strcmp
1255 }
1256 // substr <- ro is impossible.
1257 {
1258 //ryml::substr foobar = foobar_str_ro; // compile error!
1259 }
1260 }
1261
1262 // construct from char*/const char*: mutable vs immutable memory.
1263 // use to_substr()/to_csubstr()
1264 {
1265 char const* foobar_str_ro = "foobar"; // ro := read-only
1266 char foobar_str_rw_[] = "foobar"; // rw := read-write
1267 char * foobar_str_rw = foobar_str_rw_; // rw := read-write
1268 static_assert(!std::is_array<decltype(foobar_str_ro)>::value, "this is a decayed pointer");
1269 static_assert(!std::is_array<decltype(foobar_str_rw)>::value, "this is a decayed pointer");
1270 // csubstr <- read-only memory
1271 {
1272 //ryml::csubstr foobar = foobar_str_ro; // compile error: length is not known
1273 ryml::csubstr foobar = ryml::to_csubstr(foobar_str_ro);
1274 CHECK(foobar.data() == foobar_str_ro);
1275 CHECK(foobar.size() == strlen(foobar_str_ro));
1276 CHECK(foobar == "foobar"); // AKA strcmp
1277 }
1278 // csubstr <- read-write memory: you can create an immutable csubstr from mutable memory
1279 {
1280 ryml::csubstr foobar = ryml::to_csubstr(foobar_str_rw);
1281 CHECK(foobar.data() == foobar_str_rw);
1282 CHECK(foobar.size() == strlen(foobar_str_rw));
1283 CHECK(foobar == "foobar"); // AKA strcmp
1284 }
1285 // substr <- read-write memory.
1286 {
1287 ryml::substr foobar = ryml::to_substr(foobar_str_rw);
1288 CHECK(foobar.data() == foobar_str_rw);
1289 CHECK(foobar.size() == strlen(foobar_str_rw));
1290 CHECK(foobar == "foobar"); // AKA strcmp
1291 }
1292 // substr <- read-only is impossible.
1293 {
1294 //ryml::substr foobar = ryml::to_substr(foobar_str_ro); // compile error!
1295 }
1296 }
1297
1298 // substr is mutable, without changing the size:
1299 {
1300 char buf[] = "foobar";
1301 ryml::substr foobar = buf;
1302 CHECK(foobar == "foobar");
1303 foobar[0] = 'F'; CHECK(foobar == "Foobar");
1304 foobar.back() = 'R'; CHECK(foobar == "FoobaR");
1305 foobar.reverse(); CHECK(foobar == "RabooF");
1306 foobar.reverse(); CHECK(foobar == "FoobaR");
1307 foobar.reverse_sub(1, 4); CHECK(foobar == "FabooR");
1308 foobar.reverse_sub(1, 4); CHECK(foobar == "FoobaR");
1309 foobar.reverse_range(2, 5); CHECK(foobar == "FoaboR");
1310 foobar.reverse_range(2, 5); CHECK(foobar == "FoobaR");
1311 foobar.replace('o', '0'); CHECK(foobar == "F00baR");
1312 foobar.replace('a', '_'); CHECK(foobar == "F00b_R");
1313 foobar.replace("_0b", 'a'); CHECK(foobar == "FaaaaR");
1314 foobar.toupper(); CHECK(foobar == "FAAAAR");
1315 foobar.tolower(); CHECK(foobar == "faaaar");
1316 foobar.fill('.'); CHECK(foobar == "......");
1317 // see also:
1318 // - .erase()
1319 // - .replace_all()
1320 }
1321
1322 // sub-views
1323 {
1324 ryml::csubstr s = "fooFOObarBAR";
1325 CHECK(s.len == 12u);
1326 // sub(): <- first,[num]
1327 CHECK(s.sub(0) == "fooFOObarBAR");
1328 CHECK(s.sub(0, 12) == "fooFOObarBAR");
1329 CHECK(s.sub(0, 3) == "foo" );
1330 CHECK(s.sub(3) == "FOObarBAR");
1331 CHECK(s.sub(3, 3) == "FOO" );
1332 CHECK(s.sub(6) == "barBAR");
1333 CHECK(s.sub(6, 3) == "bar" );
1334 CHECK(s.sub(9) == "BAR");
1335 CHECK(s.sub(9, 3) == "BAR");
1336 // first(): <- length
1337 CHECK(s.first(0) == "" );
1338 CHECK(s.first(1) == "f" );
1339 CHECK(s.first(2) != "f" );
1340 CHECK(s.first(2) == "fo" );
1341 CHECK(s.first(3) == "foo");
1342 // last(): <- length
1343 CHECK(s.last(0) == "");
1344 CHECK(s.last(1) == "R");
1345 CHECK(s.last(2) == "AR");
1346 CHECK(s.last(3) == "BAR");
1347 // range(): <- first, last
1348 CHECK(s.range(0, 12) == "fooFOObarBAR");
1349 CHECK(s.range(1, 12) == "ooFOObarBAR");
1350 CHECK(s.range(1, 11) == "ooFOObarBA" );
1351 CHECK(s.range(2, 10) == "oFOObarB" );
1352 CHECK(s.range(3, 9) == "FOObar" );
1353 // offs(): offset from beginning, end
1354 CHECK(s.offs(0, 0) == "fooFOObarBAR");
1355 CHECK(s.offs(1, 0) == "ooFOObarBAR");
1356 CHECK(s.offs(1, 1) == "ooFOObarBA" );
1357 CHECK(s.offs(2, 1) == "oFOObarBA" );
1358 CHECK(s.offs(2, 2) == "oFOObarB" );
1359 CHECK(s.offs(3, 3) == "FOObar" );
1360 // right_of(): <- pos, include_pos
1361 CHECK(s.right_of(0, true) == "fooFOObarBAR");
1362 CHECK(s.right_of(0, false) == "ooFOObarBAR");
1363 CHECK(s.right_of(1, true) == "ooFOObarBAR");
1364 CHECK(s.right_of(1, false) == "oFOObarBAR");
1365 CHECK(s.right_of(2, true) == "oFOObarBAR");
1366 CHECK(s.right_of(2, false) == "FOObarBAR");
1367 CHECK(s.right_of(3, true) == "FOObarBAR");
1368 CHECK(s.right_of(3, false) == "OObarBAR");
1369 // left_of() <- pos, include_pos
1370 CHECK(s.left_of(12, false) == "fooFOObarBAR");
1371 CHECK(s.left_of(11, true) == "fooFOObarBAR");
1372 CHECK(s.left_of(11, false) == "fooFOObarBA" );
1373 CHECK(s.left_of(10, true) == "fooFOObarBA" );
1374 CHECK(s.left_of(10, false) == "fooFOObarB" );
1375 CHECK(s.left_of( 9, true) == "fooFOObarB" );
1376 CHECK(s.left_of( 9, false) == "fooFOObar" );
1377 // left_of(),right_of() <- substr
1378 ryml::csubstr FOO = s.sub(3, 3);
1379 CHECK(s.is_super(FOO)); // required for the following
1380 CHECK(s.left_of(FOO) == "foo");
1381 CHECK(s.right_of(FOO) == "barBAR");
1382 }
1383
1384 // printing a substr/csubstr using printf-like
1385 {
1386 ryml::csubstr s = "some substring";
1387 ryml::csubstr some = s.first(4);
1388 CHECK(some == "some");
1389 CHECK(s == "some substring");
1390 // To print a csubstr using printf(), use the %.*s format specifier:
1391 {
1392 char result[32] = {0};
1393 std::snprintf(result, sizeof(result), "%.*s", (int)some.len, some.str);
1394 printf("~~~%s~~~\n", result);
1395 CHECK(ryml::to_csubstr((const char*)result) == "some");
1396 CHECK(ryml::to_csubstr((const char*)result) == some);
1397 }
1398 // But NOTE: because this is a string view type, in general
1399 // the C-string is NOT zero terminated. So NEVER printf it
1400 // directly, or it will overflow past the end of the given
1401 // substr, with a potential unbounded access. For example,
1402 // this is really bad:
1403 {
1404 char result[32] = {0};
1405 std::snprintf(result, sizeof(result), "%s", some.str); // ERROR! do not print the c-string directly
1406 CHECK(ryml::to_csubstr((const char*)result) == "some substring");
1407 CHECK(ryml::to_csubstr((const char*)result) == s);
1408 }
1409 }
1410
1411 // printing a substr/csubstr using ostreams
1412 {
1413 ryml::csubstr s = "some substring";
1414 ryml::csubstr some = s.first(4);
1415 CHECK(some == "some");
1416 CHECK(s == "some substring");
1417 // simple! just use plain operator<<
1418 {
1419 std::stringstream ss;
1420 ss << s;
1421 CHECK(ss.str() == "some substring"); // as expected
1422 CHECK(ss.str() == s); // as expected
1423 }
1424 // But NOTE: because this is a string view type, in general
1425 // the C-string is NOT zero terminated. So NEVER print it
1426 // directly, or it will overflow past the end of the given
1427 // substr, with a potential unbounded access. For example,
1428 // this is bad:
1429 {
1430 std::stringstream ss;
1431 ss << some.str; // ERROR! do not print the c-string directly
1432 CHECK(ss.str() == "some substring"); // NOT "some"
1433 CHECK(ss.str() == s); // NOT some
1434 }
1435 // this is also bad (the same)
1436 {
1437 std::stringstream ss;
1438 ss << some.data(); // ERROR! do not print the c-string directly
1439 CHECK(ss.str() == "some substring"); // NOT "some"
1440 CHECK(ss.str() == s); // NOT some
1441 }
1442 // this is ok:
1443 {
1444 std::stringstream ss;
1445 ss << some;
1446 CHECK(ss.str() == "some"); // ok
1447 CHECK(ss.str() == some); // ok
1448 }
1449 }
1450
1451 // is_sub(),is_super()
1452 {
1453 ryml::csubstr foobar = "foobar";
1454 ryml::csubstr foo = foobar.first(3);
1455 CHECK(foo.is_sub(foobar));
1456 CHECK(foo.is_sub(foo));
1457 CHECK(!foo.is_super(foobar));
1458 CHECK(!foobar.is_sub(foo));
1459 // identity comparison is true:
1460 CHECK(foo.is_super(foo));
1461 CHECK(foo.is_sub(foo));
1462 CHECK(foobar.is_sub(foobar));
1463 CHECK(foobar.is_super(foobar));
1464 }
1465
1466 // overlaps()
1467 {
1468 ryml::csubstr foobar = "foobar";
1469 ryml::csubstr foo = foobar.first(3);
1470 ryml::csubstr oba = foobar.offs(2, 1);
1471 ryml::csubstr abc = "abc";
1472 CHECK(foobar.overlaps(foo));
1473 CHECK(foobar.overlaps(oba));
1474 CHECK(foo.overlaps(foobar));
1475 CHECK(foo.overlaps(oba));
1476 CHECK(!foo.overlaps(abc));
1477 CHECK(!abc.overlaps(foo));
1478 }
1479
1480 // triml(): trim characters from the left
1481 // trimr(): trim characters from the right
1482 // trim(): trim characters from left AND right
1483 {
1484 CHECK(ryml::csubstr(" \t\n\rcontents without whitespace\t \n\r").trim("\t \n\r") == "contents without whitespace");
1485 ryml::csubstr aaabbb = "aaabbb";
1486 ryml::csubstr aaa___bbb = "aaa___bbb";
1487 // trim a character:
1488 CHECK(aaabbb.triml('a') == aaabbb.last(3)); // bbb
1489 CHECK(aaabbb.trimr('a') == aaabbb);
1490 CHECK(aaabbb.trim ('a') == aaabbb.last(3)); // bbb
1491 CHECK(aaabbb.triml('b') == aaabbb);
1492 CHECK(aaabbb.trimr('b') == aaabbb.first(3)); // aaa
1493 CHECK(aaabbb.trim ('b') == aaabbb.first(3)); // aaa
1494 CHECK(aaabbb.triml('c') == aaabbb);
1495 CHECK(aaabbb.trimr('c') == aaabbb);
1496 CHECK(aaabbb.trim ('c') == aaabbb);
1497 CHECK(aaa___bbb.triml('a') == aaa___bbb.last(6)); // ___bbb
1498 CHECK(aaa___bbb.trimr('a') == aaa___bbb);
1499 CHECK(aaa___bbb.trim ('a') == aaa___bbb.last(6)); // ___bbb
1500 CHECK(aaa___bbb.triml('b') == aaa___bbb);
1501 CHECK(aaa___bbb.trimr('b') == aaa___bbb.first(6)); // aaa___
1502 CHECK(aaa___bbb.trim ('b') == aaa___bbb.first(6)); // aaa___
1503 CHECK(aaa___bbb.triml('c') == aaa___bbb);
1504 CHECK(aaa___bbb.trimr('c') == aaa___bbb);
1505 CHECK(aaa___bbb.trim ('c') == aaa___bbb);
1506 // trim ANY of the characters:
1507 CHECK(aaabbb.triml("ab") == "");
1508 CHECK(aaabbb.trimr("ab") == "");
1509 CHECK(aaabbb.trim ("ab") == "");
1510 CHECK(aaabbb.triml("ba") == "");
1511 CHECK(aaabbb.trimr("ba") == "");
1512 CHECK(aaabbb.trim ("ba") == "");
1513 CHECK(aaabbb.triml("cd") == aaabbb);
1514 CHECK(aaabbb.trimr("cd") == aaabbb);
1515 CHECK(aaabbb.trim ("cd") == aaabbb);
1516 CHECK(aaa___bbb.triml("ab") == aaa___bbb.last(6)); // ___bbb
1517 CHECK(aaa___bbb.triml("ba") == aaa___bbb.last(6)); // ___bbb
1518 CHECK(aaa___bbb.triml("cd") == aaa___bbb);
1519 CHECK(aaa___bbb.trimr("ab") == aaa___bbb.first(6)); // aaa___
1520 CHECK(aaa___bbb.trimr("ba") == aaa___bbb.first(6)); // aaa___
1521 CHECK(aaa___bbb.trimr("cd") == aaa___bbb);
1522 CHECK(aaa___bbb.trim ("ab") == aaa___bbb.range(3, 6)); // ___
1523 CHECK(aaa___bbb.trim ("ba") == aaa___bbb.range(3, 6)); // ___
1524 CHECK(aaa___bbb.trim ("cd") == aaa___bbb);
1525 }
1526
1527 // unquoted():
1528 {
1529 CHECK(ryml::csubstr( "'this is is single quoted'" ).unquoted() == "this is is single quoted");
1530 CHECK(ryml::csubstr("\"this is is double quoted\"").unquoted() == "this is is double quoted");
1531 }
1532
1533 // stripl(): remove pattern from the left
1534 // stripr(): remove pattern from the right
1535 {
1536 ryml::csubstr abc___cba = "abc___cba";
1537 ryml::csubstr abc___abc = "abc___abc";
1538 CHECK(abc___cba.stripl("abc") == abc___cba.last(6)); // ___cba
1539 CHECK(abc___cba.stripr("abc") == abc___cba);
1540 CHECK(abc___cba.stripl("ab") == abc___cba.last(7)); // c___cba
1541 CHECK(abc___cba.stripr("ab") == abc___cba);
1542 CHECK(abc___cba.stripl("a") == abc___cba.last(8)); // bc___cba, same as triml('a')
1543 CHECK(abc___cba.stripr("a") == abc___cba.first(8));
1544 CHECK(abc___abc.stripl("abc") == abc___abc.last(6)); // ___abc
1545 CHECK(abc___abc.stripr("abc") == abc___abc.first(6)); // abc___
1546 CHECK(abc___abc.stripl("ab") == abc___abc.last(7)); // c___cba
1547 CHECK(abc___abc.stripr("ab") == abc___abc);
1548 CHECK(abc___abc.stripl("a") == abc___abc.last(8)); // bc___cba, same as triml('a')
1549 CHECK(abc___abc.stripr("a") == abc___abc);
1550 }
1551
1552 // begins_with()/ends_with()
1553 // begins_with_any()/ends_with_any()
1554 {
1555 ryml::csubstr s = "foobar123";
1556 // char overloads
1557 CHECK(s.begins_with('f'));
1558 CHECK(s.ends_with('3'));
1559 CHECK(!s.ends_with('2'));
1560 CHECK(!s.ends_with('o'));
1561 // char[] overloads
1562 CHECK(s.begins_with("foobar"));
1563 CHECK(s.begins_with("foo"));
1564 CHECK(s.begins_with_any("foo"));
1565 CHECK(!s.begins_with("oof"));
1566 CHECK(s.begins_with_any("oof"));
1567 CHECK(s.ends_with("23"));
1568 CHECK(s.ends_with("123"));
1569 CHECK(s.ends_with_any("123"));
1570 CHECK(!s.ends_with("321"));
1571 CHECK(s.ends_with_any("231"));
1572 }
1573
1574 // select()
1575 {
1576 ryml::csubstr s = "0123456789";
1577 CHECK(s.select('0') == s.sub(0, 1));
1578 CHECK(s.select('1') == s.sub(1, 1));
1579 CHECK(s.select('2') == s.sub(2, 1));
1580 CHECK(s.select('8') == s.sub(8, 1));
1581 CHECK(s.select('9') == s.sub(9, 1));
1582 CHECK(s.select("0123") == s.range(0, 4));
1583 CHECK(s.select("012" ) == s.range(0, 3));
1584 CHECK(s.select("01" ) == s.range(0, 2));
1585 CHECK(s.select("0" ) == s.range(0, 1));
1586 CHECK(s.select( "123") == s.range(1, 4));
1587 CHECK(s.select( "23") == s.range(2, 4));
1588 CHECK(s.select( "3") == s.range(3, 4));
1589 }
1590
1591 // find()
1592 {
1593 ryml::csubstr s012345 = "012345";
1594 // find single characters:
1595 CHECK(s012345.find('a') == ryml::npos);
1596 CHECK(s012345.find('0' ) == 0u);
1597 CHECK(s012345.find('0', 1u) == ryml::npos);
1598 CHECK(s012345.find('1' ) == 1u);
1599 CHECK(s012345.find('1', 2u) == ryml::npos);
1600 CHECK(s012345.find('2' ) == 2u);
1601 CHECK(s012345.find('2', 3u) == ryml::npos);
1602 CHECK(s012345.find('3' ) == 3u);
1603 CHECK(s012345.find('3', 4u) == ryml::npos);
1604 // find patterns
1605 CHECK(s012345.find("ab" ) == ryml::npos);
1606 CHECK(s012345.find("01" ) == 0u);
1607 CHECK(s012345.find("01", 1u) == ryml::npos);
1608 CHECK(s012345.find("12" ) == 1u);
1609 CHECK(s012345.find("12", 2u) == ryml::npos);
1610 CHECK(s012345.find("23" ) == 2u);
1611 CHECK(s012345.find("23", 3u) == ryml::npos);
1612 }
1613
1614 // count(): count the number of occurrences of a character
1615 {
1616 ryml::csubstr buf = "00110022003300440055";
1617 CHECK(buf.count('1' ) == 2u);
1618 CHECK(buf.count('1', 0u) == 2u);
1619 CHECK(buf.count('1', 1u) == 2u);
1620 CHECK(buf.count('1', 2u) == 2u);
1621 CHECK(buf.count('1', 3u) == 1u);
1622 CHECK(buf.count('1', 4u) == 0u);
1623 CHECK(buf.count('1', 5u) == 0u);
1624 CHECK(buf.count('0' ) == 10u);
1625 CHECK(buf.count('0', 0u) == 10u);
1626 CHECK(buf.count('0', 1u) == 9u);
1627 CHECK(buf.count('0', 2u) == 8u);
1628 CHECK(buf.count('0', 3u) == 8u);
1629 CHECK(buf.count('0', 4u) == 8u);
1630 CHECK(buf.count('0', 5u) == 7u);
1631 CHECK(buf.count('0', 6u) == 6u);
1632 CHECK(buf.count('0', 7u) == 6u);
1633 CHECK(buf.count('0', 8u) == 6u);
1634 CHECK(buf.count('0', 9u) == 5u);
1635 CHECK(buf.count('0', 10u) == 4u);
1636 CHECK(buf.count('0', 11u) == 4u);
1637 CHECK(buf.count('0', 12u) == 4u);
1638 CHECK(buf.count('0', 13u) == 3u);
1639 CHECK(buf.count('0', 14u) == 2u);
1640 CHECK(buf.count('0', 15u) == 2u);
1641 CHECK(buf.count('0', 16u) == 2u);
1642 CHECK(buf.count('0', 17u) == 1u);
1643 CHECK(buf.count('0', 18u) == 0u);
1644 CHECK(buf.count('0', 19u) == 0u);
1645 CHECK(buf.count('0', 20u) == 0u);
1646 }
1647
1648 // first_of(),last_of()
1649 {
1650 ryml::csubstr s012345 = "012345";
1651 CHECK(s012345.first_of('a') == ryml::npos);
1652 CHECK(s012345.first_of("ab") == ryml::npos);
1653 CHECK(s012345.first_of('0') == 0u);
1654 CHECK(s012345.first_of("0") == 0u);
1655 CHECK(s012345.first_of("01") == 0u);
1656 CHECK(s012345.first_of("10") == 0u);
1657 CHECK(s012345.first_of("012") == 0u);
1658 CHECK(s012345.first_of("210") == 0u);
1659 CHECK(s012345.first_of("0123") == 0u);
1660 CHECK(s012345.first_of("3210") == 0u);
1661 CHECK(s012345.first_of("01234") == 0u);
1662 CHECK(s012345.first_of("43210") == 0u);
1663 CHECK(s012345.first_of("012345") == 0u);
1664 CHECK(s012345.first_of("543210") == 0u);
1665 CHECK(s012345.first_of('5') == 5u);
1666 CHECK(s012345.first_of("5") == 5u);
1667 CHECK(s012345.first_of("45") == 4u);
1668 CHECK(s012345.first_of("54") == 4u);
1669 CHECK(s012345.first_of("345") == 3u);
1670 CHECK(s012345.first_of("543") == 3u);
1671 CHECK(s012345.first_of("2345") == 2u);
1672 CHECK(s012345.first_of("5432") == 2u);
1673 CHECK(s012345.first_of("12345") == 1u);
1674 CHECK(s012345.first_of("54321") == 1u);
1675 CHECK(s012345.first_of("012345") == 0u);
1676 CHECK(s012345.first_of("543210") == 0u);
1677 CHECK(s012345.first_of('0', 6u) == ryml::npos);
1678 CHECK(s012345.first_of('5', 6u) == ryml::npos);
1679 CHECK(s012345.first_of("012345", 6u) == ryml::npos);
1680 //
1681 CHECK(s012345.last_of('a') == ryml::npos);
1682 CHECK(s012345.last_of("ab") == ryml::npos);
1683 CHECK(s012345.last_of('0') == 0u);
1684 CHECK(s012345.last_of("0") == 0u);
1685 CHECK(s012345.last_of("01") == 1u);
1686 CHECK(s012345.last_of("10") == 1u);
1687 CHECK(s012345.last_of("012") == 2u);
1688 CHECK(s012345.last_of("210") == 2u);
1689 CHECK(s012345.last_of("0123") == 3u);
1690 CHECK(s012345.last_of("3210") == 3u);
1691 CHECK(s012345.last_of("01234") == 4u);
1692 CHECK(s012345.last_of("43210") == 4u);
1693 CHECK(s012345.last_of("012345") == 5u);
1694 CHECK(s012345.last_of("543210") == 5u);
1695 CHECK(s012345.last_of('5') == 5u);
1696 CHECK(s012345.last_of("5") == 5u);
1697 CHECK(s012345.last_of("45") == 5u);
1698 CHECK(s012345.last_of("54") == 5u);
1699 CHECK(s012345.last_of("345") == 5u);
1700 CHECK(s012345.last_of("543") == 5u);
1701 CHECK(s012345.last_of("2345") == 5u);
1702 CHECK(s012345.last_of("5432") == 5u);
1703 CHECK(s012345.last_of("12345") == 5u);
1704 CHECK(s012345.last_of("54321") == 5u);
1705 CHECK(s012345.last_of("012345") == 5u);
1706 CHECK(s012345.last_of("543210") == 5u);
1707 CHECK(s012345.last_of('0', 6u) == 0u);
1708 CHECK(s012345.last_of('5', 6u) == 5u);
1709 CHECK(s012345.last_of("012345", 6u) == 5u);
1710 }
1711
1712 // first_not_of(), last_not_of()
1713 {
1714 ryml::csubstr s012345 = "012345";
1715 CHECK(s012345.first_not_of('a') == 0u);
1716 CHECK(s012345.first_not_of("ab") == 0u);
1717 CHECK(s012345.first_not_of('0') == 1u);
1718 CHECK(s012345.first_not_of("0") == 1u);
1719 CHECK(s012345.first_not_of("01") == 2u);
1720 CHECK(s012345.first_not_of("10") == 2u);
1721 CHECK(s012345.first_not_of("012") == 3u);
1722 CHECK(s012345.first_not_of("210") == 3u);
1723 CHECK(s012345.first_not_of("0123") == 4u);
1724 CHECK(s012345.first_not_of("3210") == 4u);
1725 CHECK(s012345.first_not_of("01234") == 5u);
1726 CHECK(s012345.first_not_of("43210") == 5u);
1727 CHECK(s012345.first_not_of("012345") == ryml::npos);
1728 CHECK(s012345.first_not_of("543210") == ryml::npos);
1729 CHECK(s012345.first_not_of('5') == 0u);
1730 CHECK(s012345.first_not_of("5") == 0u);
1731 CHECK(s012345.first_not_of("45") == 0u);
1732 CHECK(s012345.first_not_of("54") == 0u);
1733 CHECK(s012345.first_not_of("345") == 0u);
1734 CHECK(s012345.first_not_of("543") == 0u);
1735 CHECK(s012345.first_not_of("2345") == 0u);
1736 CHECK(s012345.first_not_of("5432") == 0u);
1737 CHECK(s012345.first_not_of("12345") == 0u);
1738 CHECK(s012345.first_not_of("54321") == 0u);
1739 CHECK(s012345.first_not_of("012345") == ryml::npos);
1740 CHECK(s012345.first_not_of("543210") == ryml::npos);
1741 CHECK(s012345.last_not_of('a') == 5u);
1742 CHECK(s012345.last_not_of("ab") == 5u);
1743 CHECK(s012345.last_not_of('5') == 4u);
1744 CHECK(s012345.last_not_of("5") == 4u);
1745 CHECK(s012345.last_not_of("45") == 3u);
1746 CHECK(s012345.last_not_of("54") == 3u);
1747 CHECK(s012345.last_not_of("345") == 2u);
1748 CHECK(s012345.last_not_of("543") == 2u);
1749 CHECK(s012345.last_not_of("2345") == 1u);
1750 CHECK(s012345.last_not_of("5432") == 1u);
1751 CHECK(s012345.last_not_of("12345") == 0u);
1752 CHECK(s012345.last_not_of("54321") == 0u);
1753 CHECK(s012345.last_not_of("012345") == ryml::npos);
1754 CHECK(s012345.last_not_of("543210") == ryml::npos);
1755 CHECK(s012345.last_not_of('0') == 5u);
1756 CHECK(s012345.last_not_of("0") == 5u);
1757 CHECK(s012345.last_not_of("01") == 5u);
1758 CHECK(s012345.last_not_of("10") == 5u);
1759 CHECK(s012345.last_not_of("012") == 5u);
1760 CHECK(s012345.last_not_of("210") == 5u);
1761 CHECK(s012345.last_not_of("0123") == 5u);
1762 CHECK(s012345.last_not_of("3210") == 5u);
1763 CHECK(s012345.last_not_of("01234") == 5u);
1764 CHECK(s012345.last_not_of("43210") == 5u);
1765 CHECK(s012345.last_not_of("012345") == ryml::npos);
1766 CHECK(s012345.last_not_of("543210") == ryml::npos);
1767 }
1768
1769 // first_non_empty_span()
1770 {
1771 CHECK(ryml::csubstr("foo bar").first_non_empty_span() == "foo");
1772 CHECK(ryml::csubstr(" foo bar").first_non_empty_span() == "foo");
1773 CHECK(ryml::csubstr("\n \r \t foo bar").first_non_empty_span() == "foo");
1774 CHECK(ryml::csubstr("\n \r \t foo\n\r\t bar").first_non_empty_span() == "foo");
1775 CHECK(ryml::csubstr("\n \r \t foo\n\r\t bar").first_non_empty_span() == "foo");
1776 CHECK(ryml::csubstr(",\n \r \t foo\n\r\t bar").first_non_empty_span() == ",");
1777 }
1778 // first_uint_span()
1779 {
1780 CHECK(ryml::csubstr("1234 asdkjh").first_uint_span() == "1234");
1781 CHECK(ryml::csubstr("1234\rasdkjh").first_uint_span() == "1234");
1782 CHECK(ryml::csubstr("1234\tasdkjh").first_uint_span() == "1234");
1783 CHECK(ryml::csubstr("1234\nasdkjh").first_uint_span() == "1234");
1784 CHECK(ryml::csubstr("1234]asdkjh").first_uint_span() == "1234");
1785 CHECK(ryml::csubstr("1234)asdkjh").first_uint_span() == "1234");
1786 CHECK(ryml::csubstr("1234gasdkjh").first_uint_span() == "");
1787 }
1788 // first_int_span()
1789 {
1790 CHECK(ryml::csubstr("-1234 asdkjh").first_int_span() == "-1234");
1791 CHECK(ryml::csubstr("-1234\rasdkjh").first_int_span() == "-1234");
1792 CHECK(ryml::csubstr("-1234\tasdkjh").first_int_span() == "-1234");
1793 CHECK(ryml::csubstr("-1234\nasdkjh").first_int_span() == "-1234");
1794 CHECK(ryml::csubstr("-1234]asdkjh").first_int_span() == "-1234");
1795 CHECK(ryml::csubstr("-1234)asdkjh").first_int_span() == "-1234");
1796 CHECK(ryml::csubstr("-1234gasdkjh").first_int_span() == "");
1797 }
1798 // first_real_span()
1799 {
1800 CHECK(ryml::csubstr("-1234 asdkjh").first_real_span() == "-1234");
1801 CHECK(ryml::csubstr("-1234\rasdkjh").first_real_span() == "-1234");
1802 CHECK(ryml::csubstr("-1234\tasdkjh").first_real_span() == "-1234");
1803 CHECK(ryml::csubstr("-1234\nasdkjh").first_real_span() == "-1234");
1804 CHECK(ryml::csubstr("-1234]asdkjh").first_real_span() == "-1234");
1805 CHECK(ryml::csubstr("-1234)asdkjh").first_real_span() == "-1234");
1806 CHECK(ryml::csubstr("-1234gasdkjh").first_real_span() == "");
1807 CHECK(ryml::csubstr("1.234 asdkjh").first_real_span() == "1.234");
1808 CHECK(ryml::csubstr("1.234e+5 asdkjh").first_real_span() == "1.234e+5");
1809 CHECK(ryml::csubstr("1.234e-5 asdkjh").first_real_span() == "1.234e-5");
1810 CHECK(ryml::csubstr("1.234 asdkjh").first_real_span() == "1.234");
1811 CHECK(ryml::csubstr("1.234e+5 asdkjh").first_real_span() == "1.234e+5");
1812 CHECK(ryml::csubstr("1.234e-5 asdkjh").first_real_span() == "1.234e-5");
1813 CHECK(ryml::csubstr("-1.234 asdkjh").first_real_span() == "-1.234");
1814 CHECK(ryml::csubstr("-1.234e+5 asdkjh").first_real_span() == "-1.234e+5");
1815 CHECK(ryml::csubstr("-1.234e-5 asdkjh").first_real_span() == "-1.234e-5");
1816 // hexadecimal real numbers
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 CHECK(ryml::csubstr("+0x1.e8480p+19 asdkjh").first_real_span() == "+0x1.e8480p+19");
1822 CHECK(ryml::csubstr("+0x1.e8480p-19 asdkjh").first_real_span() == "+0x1.e8480p-19");
1823 // binary real numbers
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 CHECK(ryml::csubstr("+0b101.011p+19 asdkjh").first_real_span() == "+0b101.011p+19");
1829 CHECK(ryml::csubstr("+0b101.011p-19 asdkjh").first_real_span() == "+0b101.011p-19");
1830 // octal real numbers
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 CHECK(ryml::csubstr("+0o173.045p+19 asdkjh").first_real_span() == "+0o173.045p+19");
1836 CHECK(ryml::csubstr("+0o173.045p-19 asdkjh").first_real_span() == "+0o173.045p-19");
1837 }
1838 // see also is_number()
1839
1840 // basename(), dirname(), extshort(), extlong()
1841 {
1842 CHECK(ryml::csubstr("/path/to/file.tar.gz").basename() == "file.tar.gz");
1843 CHECK(ryml::csubstr("/path/to/file.tar.gz").dirname() == "/path/to/");
1844 CHECK(ryml::csubstr("C:\\path\\to\\file.tar.gz").basename('\\') == "file.tar.gz");
1845 CHECK(ryml::csubstr("C:\\path\\to\\file.tar.gz").dirname('\\') == "C:\\path\\to\\");
1846 CHECK(ryml::csubstr("/path/to/file.tar.gz").extshort() == "gz");
1847 CHECK(ryml::csubstr("/path/to/file.tar.gz").extlong() == "tar.gz");
1848 CHECK(ryml::csubstr("/path/to/file.tar.gz").name_wo_extshort() == "/path/to/file.tar");
1849 CHECK(ryml::csubstr("/path/to/file.tar.gz").name_wo_extlong() == "/path/to/file");
1850 }
1851
1852 // split()
1853 {
1854 using namespace ryml;
1855 csubstr parts[] = {"aa", "bb", "cc", "dd", "ee", "ff"};
1856 {
1857 size_t count = 0;
1858 for(csubstr part : csubstr("aa/bb/cc/dd/ee/ff").split('/'))
1859 CHECK(part == parts[count++]);
1860 CHECK(count == 6u);
1861 }
1862 {
1863 size_t count = 0;
1864 for(csubstr part : csubstr("aa.bb.cc.dd.ee.ff").split('.'))
1865 CHECK(part == parts[count++]);
1866 CHECK(count == 6u);
1867 }
1868 {
1869 size_t count = 0;
1870 for(csubstr part : csubstr("aa-bb-cc-dd-ee-ff").split('-'))
1871 CHECK(part == parts[count++]);
1872 CHECK(count == 6u);
1873 }
1874 // see also next_split()
1875 }
1876
1877 // pop_left(), pop_right() --- non-greedy version
1878 // gpop_left(), gpop_right() --- greedy version
1879 {
1880 const bool skip_empty = true;
1881 // pop_left(): pop the last element from the left
1882 CHECK(ryml::csubstr( "0/1/2" ). pop_left('/' ) == "0" );
1883 CHECK(ryml::csubstr( "/0/1/2" ). pop_left('/' ) == "" );
1884 CHECK(ryml::csubstr("//0/1/2" ). pop_left('/' ) == "" );
1885 CHECK(ryml::csubstr( "0/1/2" ). pop_left('/', skip_empty) == "0" );
1886 CHECK(ryml::csubstr( "/0/1/2" ). pop_left('/', skip_empty) == "/0" );
1887 CHECK(ryml::csubstr("//0/1/2" ). pop_left('/', skip_empty) == "//0" );
1888 // gpop_left(): pop all but the first element (greedy pop)
1889 CHECK(ryml::csubstr( "0/1/2" ).gpop_left('/' ) == "0/1" );
1890 CHECK(ryml::csubstr( "/0/1/2" ).gpop_left('/' ) == "/0/1" );
1891 CHECK(ryml::csubstr("//0/1/2" ).gpop_left('/' ) == "//0/1" );
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('/' ) == "/0/1/2/");
1897 CHECK(ryml::csubstr("//0/1/2//" ).gpop_left('/' ) == "//0/1/2/");
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 CHECK(ryml::csubstr( "/0/1/2//" ).gpop_left('/', skip_empty) == "/0/1" );
1906 CHECK(ryml::csubstr("//0/1/2//" ).gpop_left('/', skip_empty) == "//0/1" );
1907 // pop_right(): pop the last element from the right
1908 CHECK(ryml::csubstr( "0/1/2" ). pop_right('/' ) == "2" );
1909 CHECK(ryml::csubstr( "0/1/2/" ). pop_right('/' ) == "" );
1910 CHECK(ryml::csubstr( "0/1/2//" ). pop_right('/' ) == "" );
1911 CHECK(ryml::csubstr( "0/1/2" ). pop_right('/', skip_empty) == "2" );
1912 CHECK(ryml::csubstr( "0/1/2/" ). pop_right('/', skip_empty) == "2/" );
1913 CHECK(ryml::csubstr( "0/1/2//" ). pop_right('/', skip_empty) == "2//" );
1914 // gpop_right(): pop all but the first element (greedy pop)
1915 CHECK(ryml::csubstr( "0/1/2" ).gpop_right('/' ) == "1/2" );
1916 CHECK(ryml::csubstr( "0/1/2/" ).gpop_right('/' ) == "1/2/" );
1917 CHECK(ryml::csubstr( "0/1/2//" ).gpop_right('/' ) == "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('/' ) == "/0/1/2/" );
1923 CHECK(ryml::csubstr("//0/1/2//" ).gpop_right('/' ) == "/0/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 CHECK(ryml::csubstr("//0/1/2/" ).gpop_right('/', skip_empty) == "1/2/" );
1932 CHECK(ryml::csubstr("//0/1/2//" ).gpop_right('/', skip_empty) == "1/2//");
1933 }
1934}
1935
1936
1937//-----------------------------------------------------------------------------
1938
1939
1940/** demonstrate how to load a YAML file from disk to parse with ryml.
1941 *
1942 * ryml offers no overload to directly parse files from disk; it only
1943 * parses source buffers (which may be mutable or immutable). It is
1944 * up to the caller to first load the file contents into a buffer
1945 * before parsing with ryml. To help with this you can use the
1946 * (efficient) helper [file_get_contents](@ref
1947 * c4::yml::file_get_contents()). See also the analogous
1948 * [file_put_contents](@ref c4::yml::file_put_contents())
1949 *
1950 * @see doc_parse */
1952{
1953 const char filename[] = "ryml_example.yml";
1954 std::string yaml = ""
1955 "foo: 1" "\n"
1956 "bar:" "\n"
1957 "- 2" "\n"
1958 "- 3" "\n";
1959 // because this is a minimal sample, it assumes nothing on the
1960 // environment/OS (other than that it can read/write files). So we
1961 // create the file on the fly:
1962 ryml::file_put_contents(yaml, filename);
1963
1964 // now we can load it into a std::string (for example):
1965 {
1966 std::string contents = ryml::file_get_contents<std::string>(filename);
1967 ryml::Tree tree = ryml::parse_in_arena(ryml::to_csubstr(contents)); // immutable (csubstr) overload
1968 CHECK(tree["foo"].val() == "1");
1969 CHECK(tree["bar"][0].val() == "2");
1970 CHECK(tree["bar"][1].val() == "3");
1971 }
1972
1973 // or we can use a vector<char> instead:
1974 {
1975 std::vector<char> contents = ryml::file_get_contents<std::vector<char>>(filename);
1976 ryml::Tree tree = ryml::parse_in_place(ryml::to_substr(contents)); // mutable (substr) overload
1977 CHECK(tree["foo"].val() == "1");
1978 CHECK(tree["bar"][0].val() == "2");
1979 CHECK(tree["bar"][1].val() == "3");
1980 }
1981
1982 // generally, any contiguous char container can be used with ryml,
1983 // provided that the ryml::substr/ryml::csubstr view can be
1984 // created out of it.
1985 //
1986 // ryml provides the overloads above for these two containers, but
1987 // if you are using another container it should be very easy (only
1988 // requires pointer and length).
1989}
1990
1991
1992//-----------------------------------------------------------------------------
1993
1994/** demonstrate in-place parsing of a mutable YAML source buffer.
1995 * @see doc_parse */
1997{
1998 // Like the name suggests, parse_in_place() directly mutates the
1999 // source buffer in place
2000 char src[] = "{foo: 1, bar: [2, 3]}"; // ryml can parse in situ
2001 ryml::substr srcview = src; // a mutable view to the source buffer
2002 ryml::Tree tree = ryml::parse_in_place(srcview); // you can also reuse the tree and/or parser
2003 ryml::ConstNodeRef root = tree.crootref(); // get a constant reference to the root
2004
2005 CHECK(root.is_map());
2006 CHECK(root["foo"].is_keyval());
2007 CHECK(root["foo"].key() == "foo");
2008 CHECK(root["foo"].val() == "1");
2009 CHECK(root["bar"].is_seq());
2010 CHECK(root["bar"].has_key());
2011 CHECK(root["bar"].key() == "bar");
2012 CHECK(root["bar"][0].val() == "2");
2013 CHECK(root["bar"][1].val() == "3");
2014
2015 // deserializing:
2016 int foo = 0, bar0 = 0, bar1 = 0;
2017 root["foo"].load(&foo);
2018 root["bar"][0].load(&bar0);
2019 root["bar"][1].load(&bar1);
2020 CHECK(foo == 1);
2021 CHECK(bar0 == 2);
2022 CHECK(bar1 == 3);
2023
2024 // after parsing, the tree holds views to the source buffer:
2025 CHECK(root["foo"].val().data() == src + strlen("{foo: "));
2026 CHECK(root["foo"].val().begin() == src + strlen("{foo: "));
2027 CHECK(root["foo"].val().end() == src + strlen("{foo: 1"));
2028 CHECK(root["foo"].val().is_sub(srcview)); // equivalent to the previous three assertions
2029 CHECK(root["bar"][0].val().data() == src + strlen("{foo: 1, bar: ["));
2030 CHECK(root["bar"][0].val().begin() == src + strlen("{foo: 1, bar: ["));
2031 CHECK(root["bar"][0].val().end() == src + strlen("{foo: 1, bar: [2"));
2032 CHECK(root["bar"][0].val().is_sub(srcview)); // equivalent to the previous three assertions
2033 CHECK(root["bar"][1].val().data() == src + strlen("{foo: 1, bar: [2, "));
2034 CHECK(root["bar"][1].val().begin() == src + strlen("{foo: 1, bar: [2, "));
2035 CHECK(root["bar"][1].val().end() == src + strlen("{foo: 1, bar: [2, 3"));
2036 CHECK(root["bar"][1].val().is_sub(srcview)); // equivalent to the previous three assertions
2037
2038 // NOTE. parse_in_place() cannot accept ryml::csubstr
2039 // so this will cause a /compile/ error:
2040 ryml::csubstr csrcview = srcview; // ok, can assign from mutable to immutable
2041 //tree = ryml::parse_in_place(csrcview); // compile error, cannot mutate an immutable view
2042 (void)csrcview;
2043}
2044
2045
2046//-----------------------------------------------------------------------------
2047
2048/** demonstrate parsing of a read-only YAML source buffer
2049 * @see doc_parse */
2051{
2052 // to parse read-only memory, ryml will copy first to the tree's
2053 // arena, and then parse the copied buffer:
2054 ryml::Tree tree = ryml::parse_in_arena("{foo: 1, bar: [2, 3]}");
2055 ryml::ConstNodeRef root = tree.crootref(); // get a const reference to the root
2056
2057 CHECK(root.is_map());
2058 CHECK(root["foo"].is_keyval());
2059 CHECK(root["foo"].key() == "foo");
2060 CHECK(root["foo"].val() == "1");
2061 CHECK(root["bar"].is_seq());
2062 CHECK(root["bar"].has_key());
2063 CHECK(root["bar"].key() == "bar");
2064 CHECK(root["bar"][0].val() == "2");
2065 CHECK(root["bar"][1].val() == "3");
2066
2067 // deserializing:
2068 int foo = 0, bar0 = 0, bar1 = 0;
2069 root["foo"].load(&foo);
2070 root["bar"][0].load(&bar0);
2071 root["bar"][1].load(&bar1);
2072 CHECK(foo == 1);
2073 CHECK(bar0 == 2);
2074 CHECK(bar1 == 3);
2075
2076 // NOTE. parse_in_arena() cannot accept ryml::substr. Overloads
2077 // receiving substr buffers are declared, but intentionally left
2078 // undefined, so this will cause a /linker/ error
2079 char src[] = "{foo: is it really true}";
2080 ryml::substr srcview = src;
2081 //tree = ryml::parse_in_place(srcview); // linker error, overload intentionally undefined
2082
2083 // If you really intend to parse a mutable buffer in the arena,
2084 // then simply convert it to immutable prior to calling
2085 // parse_in_arena():
2086 ryml::csubstr csrcview = srcview; // assigning from src also works
2087 tree = ryml::parse_in_arena(csrcview); // OK! csrcview is immutable
2088 CHECK(tree["foo"].val() == "is it really true");
2089}
2090
2091
2092//-----------------------------------------------------------------------------
2093
2094/** demonstrate reuse/modification of tree when parsing
2095 * @see doc_parse */
2097{
2098 ryml::Tree tree;
2099
2100 // it will always be faster if the tree's size is conveniently reserved:
2101 tree.reserve(30); // reserve 30 nodes (good enough for this sample)
2102 // if you are using the tree's arena to serialize data,
2103 // then reserve also the arena's size:
2104 tree.reserve_arena(256); // reserve 256 characters (good enough for this sample)
2105
2106 // now parse into the tree:
2107 ryml::csubstr yaml = ""
2108 "foo: 1" "\n"
2109 "bar: [2,3]" "\n"
2110 "";
2111 ryml::parse_in_arena(yaml, &tree);
2112
2113 ryml::ConstNodeRef root = tree.crootref();
2114 CHECK(root.num_children() == 2);
2115 CHECK(root.is_map());
2116 CHECK(root["foo"].is_keyval());
2117 CHECK(root["foo"].key() == "foo");
2118 CHECK(root["foo"].val() == "1");
2119 CHECK(root["bar"].is_seq());
2120 CHECK(root["bar"].has_key());
2121 CHECK(root["bar"].key() == "bar");
2122 CHECK(root["bar"][0].val() == "2");
2123 CHECK(root["bar"][1].val() == "3");
2125
2126 // WATCHOUT: parsing into an existing tree will APPEND to it:
2127 ryml::parse_in_arena("{foo2: 12, bar2: [22, 32]}", &tree);
2129 "foo: 1" "\n"
2130 "bar: [2,3]" "\n"
2131 "foo2: 12" "\n"
2132 "bar2: [22,32]" "\n"
2133 "");
2134 CHECK(root.num_children() == 4);
2135 CHECK(root["foo2"].is_keyval());
2136 CHECK(root["foo2"].key() == "foo2");
2137 CHECK(root["foo2"].val() == "12");
2138 CHECK(root["bar2"].is_seq());
2139 CHECK(root["bar2"].has_key());
2140 CHECK(root["bar2"].key() == "bar2");
2141 CHECK(root["bar2"][0].val() == "22");
2142 CHECK(root["bar2"][1].val() == "32");
2143
2144 // if you want to fully replace the tree, you need to clear first
2145 // before parsing into an existing tree:
2146 tree.clear();
2147 tree.clear_arena(); // you may or may not want to clear the arena
2148 ryml::parse_in_arena("- a\n- b\n- {x0: 1, x1: 2}", &tree);
2149 CHECK(ryml::emitrs_yaml<std::string>(tree) == "- a\n- b\n- {x0: 1,x1: 2}\n");
2150 CHECK(root.is_seq());
2151 CHECK(root[0].val() == "a");
2152 CHECK(root[1].val() == "b");
2153 CHECK(root[2].is_map());
2154 CHECK(root[2]["x0"].val() == "1");
2155 CHECK(root[2]["x1"].val() == "2");
2156
2157 // we can parse directly into a node nested deep in an existing tree:
2158 ryml::NodeRef mroot = tree.rootref(); // modifiable root
2159 ryml::parse_in_arena("champagne: Dom Perignon\ncoffee: Arabica", mroot.append_child());
2161 "- a" "\n"
2162 "- b" "\n"
2163 "- {x0: 1,x1: 2}" "\n"
2164 "- champagne: Dom Perignon" "\n"
2165 " coffee: Arabica" "\n"
2166 "");
2167 CHECK(root.is_seq());
2168 CHECK(root[0].val() == "a");
2169 CHECK(root[1].val() == "b");
2170 CHECK(root[2].is_map());
2171 CHECK(root[2]["x0"].val() == "1");
2172 CHECK(root[2]["x1"].val() == "2");
2173 CHECK(root[3].is_map());
2174 CHECK(root[3]["champagne"].val() == "Dom Perignon");
2175 CHECK(root[3]["coffee"].val() == "Arabica");
2176
2177 mroot[3]["more"].set_map();
2178 mroot[3]["beer"].set_seq();
2179 CHECK(mroot[3]["more"].readable());
2180 CHECK(mroot[3]["more"].key() == "more");
2181 CHECK(mroot[3]["more"].is_map());
2182 CHECK(!mroot[3]["more"].is_val());
2183 ryml::parse_in_arena("{vinho verde: Soalheiro, vinho tinto: Redoma 2017}", mroot[3]["more"]);
2184 ryml::parse_in_arena("- Rochefort 10\n- Busch\n- Leffe Rituel", mroot[3]["beer"]);
2185 ryml::parse_in_arena("lots\nof\nwater", mroot[3]["always"]);
2187 "- a" "\n"
2188 "- b" "\n"
2189 "- {x0: 1,x1: 2}" "\n"
2190 "- champagne: Dom Perignon" "\n"
2191 " coffee: Arabica" "\n"
2192 // note these were added:
2193 " more:" "\n"
2194 " vinho verde: Soalheiro" "\n"
2195 " vinho tinto: Redoma 2017" "\n"
2196 " beer:" "\n"
2197 " - Rochefort 10" "\n"
2198 " - Busch" "\n"
2199 " - Leffe Rituel" "\n"
2200 " always: lots of water" "\n"
2201 "");
2202
2203 // can append at the top:
2204 ryml::parse_in_arena("- foo\n- bar\n- baz\n- bat", mroot);
2206 "- a" "\n"
2207 "- b" "\n"
2208 "- {x0: 1,x1: 2}" "\n"
2209 "- champagne: Dom Perignon" "\n"
2210 " coffee: Arabica" "\n"
2211 " more:" "\n"
2212 " vinho verde: Soalheiro" "\n"
2213 " vinho tinto: Redoma 2017" "\n"
2214 " beer:" "\n"
2215 " - Rochefort 10" "\n"
2216 " - Busch" "\n"
2217 " - Leffe Rituel" "\n"
2218 " always: lots of water" "\n"
2219 // note these were added
2220 "- foo" "\n"
2221 "- bar" "\n"
2222 "- baz" "\n"
2223 "- bat" "\n"
2224 "");
2225
2226 // or nested:
2227 ryml::parse_in_arena("[Kasteel Donker]", mroot[3]["beer"]);
2229 "- a" "\n"
2230 "- b" "\n"
2231 "- {x0: 1,x1: 2}" "\n"
2232 "- champagne: Dom Perignon" "\n"
2233 " coffee: Arabica" "\n"
2234 " more:" "\n"
2235 " vinho verde: Soalheiro" "\n"
2236 " vinho tinto: Redoma 2017" "\n"
2237 " beer:" "\n"
2238 " - Rochefort 10" "\n"
2239 " - Busch" "\n"
2240 " - Leffe Rituel" "\n"
2241 " - Kasteel Donker" "\n" // added this
2242 " always: lots of water" "\n"
2243 "- foo" "\n"
2244 "- bar" "\n"
2245 "- baz" "\n"
2246 "- bat" "\n"
2247 "");
2248}
2249
2250
2251//-----------------------------------------------------------------------------
2252
2253/** Demonstrates reuse of an existing parser. Doing this is
2254 * recommended when multiple files are parsed.
2255 * @see doc_parse */
2257{
2258 ryml::EventHandlerTree evt_handler = {};
2259 ryml::Parser parser(&evt_handler);
2260
2261 // it is also advised to reserve the parser depth
2262 // to the expected depth of the data tree:
2263 parser.reserve_stack(10); // uses small storage optimization
2264 // defaulting to 16 depth, so this
2265 // instruction is a no-op, and the stack
2266 // will located in the parser object.
2267 parser.reserve_stack(20); // But this will cause an allocation
2268 // because it is above 16.
2269
2270 ryml::csubstr yaml = "[Dom Perignon,Gosset Grande Reserve,Jacquesson 742]";
2271 ryml::Tree champagnes = parse_in_arena(&parser, "champagnes.yml", yaml);
2272 CHECK(ryml::emitrs_yaml<std::string>(champagnes) == yaml);
2273
2274 yaml = "[Rochefort 10,Busch,Leffe Rituel,Kasteel Donker]";
2275 ryml::Tree beers = parse_in_arena(&parser, "beers.yml", yaml);
2276 CHECK(ryml::emitrs_yaml<std::string>(beers) == yaml);
2277}
2278
2279
2280//-----------------------------------------------------------------------------
2281
2282/** for ultimate speed when parsing multiple times, reuse both the
2283 * tree and parser
2284 * @see doc_parse */
2286{
2287 ryml::Tree tree;
2288 // it will always be faster if the tree's size is conveniently reserved:
2289 tree.reserve(30); // reserve 30 nodes (good enough for this sample)
2290 // if you are using the tree's arena to serialize data,
2291 // then reserve also the arena's size:
2292 tree.reserve(256); // reserve 256 characters (good enough for this sample)
2293
2294 ryml::EventHandlerTree evt_handler;
2295 ryml::Parser parser(&evt_handler);
2296 // it is also advised to reserve the parser depth
2297 // to the expected depth of the data tree:
2298 parser.reserve_stack(10); // the parser uses small storage
2299 // optimization defaulting to 16 depth,
2300 // so this instruction is a no-op, and
2301 // the stack will be located in the
2302 // parser object.
2303 parser.reserve_stack(20); // But this will cause an allocation
2304 // because it is above 16.
2305
2306 ryml::csubstr champagnes = ""
2307 "- Dom Perignon\n"
2308 "- Gosset Grande Reserve\n"
2309 "- Jacquesson 742\n"
2310 "";
2311 ryml::csubstr beers = ""
2312 "- Rochefort 10\n"
2313 "- Busch\n"
2314 "- Leffe Rituel\n"
2315 "- Kasteel Donker\n"
2316 "";
2317 ryml::csubstr wines = ""
2318 "- Soalheiro\n"
2319 "- Niepoort Redoma 2017\n"
2320 "- Vina Esmeralda\n"
2321 "";
2322
2323 parse_in_arena(&parser, "champagnes.yml", champagnes, &tree);
2324 CHECK(ryml::emitrs_yaml<std::string>(tree) == champagnes);
2325
2326 // watchout: this will APPEND to the given tree:
2327 parse_in_arena(&parser, "beers.yml", beers, &tree);
2329 "- Dom Perignon\n"
2330 "- Gosset Grande Reserve\n"
2331 "- Jacquesson 742\n"
2332 "- Rochefort 10\n"
2333 "- Busch\n"
2334 "- Leffe Rituel\n"
2335 "- Kasteel Donker\n"
2336 "");
2337
2338 // if you don't wish to append, clear the tree first:
2339 tree.clear();
2340 parse_in_arena(&parser, "wines.yml", wines, &tree);
2342 "- Soalheiro\n"
2343 "- Niepoort Redoma 2017\n"
2344 "- Vina Esmeralda\n"
2345 "");
2346}
2347
2348
2349//-----------------------------------------------------------------------------
2350
2351/** shows how rapidyaml marks the tree nodes with their original style
2352 * in the parsed YAML.
2353 *
2354 * @see doc_tree
2355 * @see doc_node_classes
2356 */
2358{
2359 ryml::csubstr yaml = ""
2360 "doe: a deer, a female deer" "\n"
2361 "ray: 'a drop of golden sun'" "\n"
2362 "me: \"a name I call myself\"" "\n"
2363 "far: |-" "\n"
2364 " a long long way to go" "\n"
2365 "sow: >-" "\n"
2366 " a needle pulling thread" "\n"
2367 "seq: [0,1,2,3]" "\n"
2368 "map: {" "\n"
2369 " 0: 10," "\n"
2370 " 1: 11," "\n"
2371 " 2: 12," "\n"
2372 " 3: 13" "\n"
2373 " }" "\n"
2374 "";
2375 const ryml::Tree tree = ryml::parse_in_arena(yaml);
2376 // note how every node is tagged with its original style:
2377 CHECK(tree["doe"].is_val_plain());
2378 CHECK(tree["ray"].is_val_squo());
2379 CHECK(tree["me"].is_val_dquo());
2380 CHECK(tree["far"].is_val_literal());
2381 CHECK(tree["sow"].is_val_folded());
2382 CHECK(tree["seq"].is_flow());
2383 CHECK(tree["seq"].is_flow_sl());
2384 CHECK(tree["map"].is_flow());
2385 CHECK(tree["map"].is_flow_mlx());
2386 CHECK(tree.rootref().is_block());
2387 // ... which results in roundtrip-stable YAML:
2389}
2390
2391
2392//-----------------------------------------------------------------------------
2393
2394/** shows how to programatically iterate through trees
2395 * @see doc_tree
2396 * @see doc_node_classes
2397 */
2399{
2400 const ryml::Tree tree = ryml::parse_in_arena(
2401 "doe: a deer, a female deer" "\n"
2402 "ray: a drop of golden sun" "\n"
2403 "pi: 3.14159" "\n"
2404 "xmas: true" "\n"
2405 "french-hens: 3" "\n"
2406 "calling-birds:" "\n"
2407 " - huey" "\n"
2408 " - dewey" "\n"
2409 " - louie" "\n"
2410 " - fred" "\n"
2411 "xmas-fifth-day:" "\n"
2412 " calling-birds: four" "\n"
2413 " french-hens: 3" "\n"
2414 " golden-rings: 5" "\n"
2415 " partridges:" "\n"
2416 " count: 1" "\n"
2417 " location: a pear tree" "\n"
2418 " turtle-doves: two" "\n"
2419 "cars: GTO" "\n"
2420 "");
2421 ryml::ConstNodeRef root = tree.crootref();
2422
2423 // iterate children
2424 {
2425 std::vector<ryml::csubstr> keys, vals; // to store all the root-level keys, vals
2426 for(ryml::ConstNodeRef n : root.children())
2427 {
2428 keys.emplace_back(n.key());
2429 vals.emplace_back(n.has_val() ? n.val() : ryml::csubstr{});
2430 }
2431 CHECK(keys.size() >= 6);
2432 CHECK(vals.size() >= 6);
2433 if(keys.size() >= 6 && vals.size() >= 6)
2434 {
2435 CHECK(keys[0] == "doe");
2436 CHECK(vals[0] == "a deer, a female deer");
2437 CHECK(keys[1] == "ray");
2438 CHECK(vals[1] == "a drop of golden sun");
2439 CHECK(keys[2] == "pi");
2440 CHECK(vals[2] == "3.14159");
2441 CHECK(keys[3] == "xmas");
2442 CHECK(vals[3] == "true");
2443 CHECK(root[5].has_key());
2444 CHECK(root[5].is_seq());
2445 CHECK(root[5].key() == "calling-birds");
2446 CHECK(!root[5].has_val()); // it is a map, so not a val
2447 //CHECK(root[5].val() == ""); // ERROR! node does not have a val.
2448 CHECK(keys[5] == "calling-birds");
2449 CHECK(vals[5] == "");
2450 }
2451 }
2452
2453 // iterate siblings
2454 {
2455 size_t count = 0;
2456 ryml::csubstr calling_birds[] = {"huey", "dewey", "louie", "fred"};
2457 for(ryml::ConstNodeRef n : root["calling-birds"][2].siblings())
2458 CHECK(n.val() == calling_birds[count++]);
2459 CHECK(count == 4u);
2460 }
2461}
2462
2463
2464//-----------------------------------------------------------------------------
2465
2466/** demonstrates how to obtain the (zero-based) location of a node
2467 * from a recently parsed tree
2468 *
2469 * @see RYML_LOCATIONS_SMALL_THRESHOLD
2470 * */
2472{
2473 // NOTE: locations are zero-based. If you intend to show the
2474 // location to a human user, you may want to pre-increment the line
2475 // and column by 1.
2476 ryml::csubstr yaml = ""
2477 "{" "\n"
2478 "aa: contents," "\n"
2479 "foo: [one, [two, three]]" "\n"
2480 "}" "\n"
2481 "";
2482 // A parser is needed to track locations, and it has to be
2483 // explicitly set to do it. Location tracking is disabled by
2484 // default.
2485 ryml::ParserOptions opts = {};
2486 opts.locations(true); // enable locations, default is false
2487 ryml::EventHandlerTree evt_handler = {};
2488 ryml::Parser parser(&evt_handler, opts);
2489 CHECK(parser.options().locations());
2490 // When locations are enabled, the first task while parsing will
2491 // consist of building and caching (in the parser) a
2492 // source-to-node lookup structure to accelerate location lookups.
2493 //
2494 // The cost of building the location accelerator is linear in the
2495 // size of the source buffer. This increased cost is the reason
2496 // for the opt-in requirement. When locations are disabled there
2497 // is no cost.
2498 //
2499 // Building the location accelerator may trigger an allocation,
2500 // but this can and should be avoided by reserving prior to
2501 // parsing:
2502 parser.reserve_locations(50u); // reserve for 50 lines of YAML code
2503 // Now the structure will be built during parsing:
2504 ryml::Tree tree = parse_in_arena(&parser, "source.yml", yaml);
2505 // After this, we are ready to query the location from the parser:
2506 ryml::Location loc = tree.rootref().location(parser);
2507 // As for the complexity of the query: for large buffers it is
2508 // O(log(numlines)). For short source buffers
2509 // (RYML_LOCATIONS_SMALL_THRESHOLD lines and less), it is
2510 // O(numlines), as a plain linear search is faster in this case.
2511 CHECK(parser.location_contents(loc).begins_with("{"));
2512 CHECK(loc.offset == 0u);
2513 CHECK(loc.line == 0u);
2514 CHECK(loc.col == 0u);
2515 // on the next call, we only pay O(log(numlines)) because the
2516 // rebuild is already available:
2517 loc = tree["aa"].location(parser);
2518 CHECK(parser.location_contents(loc).begins_with("aa"));
2519 CHECK(loc.offset == 2u);
2520 CHECK(loc.line == 1u);
2521 CHECK(loc.col == 0u);
2522 // KEYSEQ in flow style: points at the key
2523 loc = tree["foo"].location(parser);
2524 CHECK(parser.location_contents(loc).begins_with("foo"));
2525 CHECK(loc.offset == 16u);
2526 CHECK(loc.line == 2u);
2527 CHECK(loc.col == 0u);
2528 loc = tree["foo"][0].location(parser);
2529 CHECK(parser.location_contents(loc).begins_with("one"));
2530 CHECK(loc.line == 2u);
2531 CHECK(loc.col == 6u);
2532 // SEQ in flow style: location points at the opening '[' (there's no key)
2533 loc = tree["foo"][1].location(parser);
2534 CHECK(parser.location_contents(loc).begins_with("["));
2535 CHECK(loc.line == 2u);
2536 CHECK(loc.col == 11u);
2537 loc = tree["foo"][1][0].location(parser);
2538 CHECK(parser.location_contents(loc).begins_with("two"));
2539 CHECK(loc.line == 2u);
2540 CHECK(loc.col == 12u);
2541 loc = tree["foo"][1][1].location(parser);
2542 CHECK(parser.location_contents(loc).begins_with("three"));
2543 CHECK(loc.line == 2u);
2544 CHECK(loc.col == 17u);
2545 // NOTE. The parser locations always point at the latest buffer to
2546 // be parsed with the parser object, so they must be queried using
2547 // the corresponding latest tree to be parsed. This means that if
2548 // the parser is reused, earlier trees will loose the possibility
2549 // of querying for location. It is undefined behavior to query the
2550 // parser for the location of a node from an earlier tree:
2551 ryml::Tree docval = parse_in_arena(&parser, "docval.yaml", "this is a docval");
2552 // From now on, none of the locations from the previous tree can
2553 // be queried:
2554 //loc = tree.rootref().location(parser); // ERROR, undefined behavior
2555 loc = docval.rootref().location(parser); // OK. this is the latest tree from this parser
2556 CHECK(parser.location_contents(loc).begins_with("this is a docval"));
2557 CHECK(loc.line == 0u);
2558 CHECK(loc.col == 0u);
2559
2560 // NOTES ABOUT CONTAINER LOCATIONS
2561 ryml::Tree tree2 = parse_in_arena(&parser, "containers.yaml",
2562 "" "\n"
2563 "a new: buffer" "\n"
2564 "to: be parsed" "\n"
2565 "map with key:" "\n"
2566 " first: value" "\n"
2567 " second: value" "\n"
2568 "seq with key:" "\n"
2569 " - first value" "\n"
2570 " - second value" "\n"
2571 " -" "\n"
2572 " - nested first value" "\n"
2573 " - nested second value" "\n"
2574 " -" "\n"
2575 " nested first: value" "\n"
2576 " nested second: value" "\n"
2577 "");
2578 // (Likewise, the docval tree can no longer be used to query.)
2579 //
2580 // For key-less block-style maps, the location of the container
2581 // points at the first child's key. For example, in this case
2582 // the root does not have a key, so its location is taken
2583 // to be at the first child:
2584 loc = tree2.rootref().location(parser);
2585 CHECK(parser.location_contents(loc).begins_with("a new"));
2586 CHECK(loc.offset == 1u);
2587 CHECK(loc.line == 1u);
2588 CHECK(loc.col == 0u);
2589 // note the first child points exactly at the same place:
2590 loc = tree2["a new"].location(parser);
2591 CHECK(parser.location_contents(loc).begins_with("a new"));
2592 CHECK(loc.offset == 1u);
2593 CHECK(loc.line == 1u);
2594 CHECK(loc.col == 0u);
2595 loc = tree2["to"].location(parser);
2596 CHECK(parser.location_contents(loc).begins_with("to"));
2597 CHECK(loc.line == 2u);
2598 CHECK(loc.col == 0u);
2599 // but of course, if the block-style map is a KEYMAP, then the
2600 // location is the map's key, and not the first child's key:
2601 loc = tree2["map with key"].location(parser);
2602 CHECK(parser.location_contents(loc).begins_with("map with key"));
2603 CHECK(loc.line == 3u);
2604 CHECK(loc.col == 0u);
2605 loc = tree2["map with key"]["first"].location(parser);
2606 CHECK(parser.location_contents(loc).begins_with("first"));
2607 CHECK(loc.line == 4u);
2608 CHECK(loc.col == 2u);
2609 loc = tree2["map with key"]["second"].location(parser);
2610 CHECK(parser.location_contents(loc).begins_with("second"));
2611 CHECK(loc.line == 5u);
2612 CHECK(loc.col == 2u);
2613 // same thing for KEYSEQ:
2614 loc = tree2["seq with key"].location(parser);
2615 CHECK(parser.location_contents(loc).begins_with("seq with key"));
2616 CHECK(loc.line == 6u);
2617 CHECK(loc.col == 0u);
2618 loc = tree2["seq with key"][0].location(parser);
2619 CHECK(parser.location_contents(loc).begins_with("first value"));
2620 CHECK(loc.line == 7u);
2621 CHECK(loc.col == 4u);
2622 loc = tree2["seq with key"][1].location(parser);
2623 CHECK(parser.location_contents(loc).begins_with("second value"));
2624 CHECK(loc.line == 8u);
2625 CHECK(loc.col == 4u);
2626 // SEQ nested in SEQ: container location points at the first child's "- " dash
2627 loc = tree2["seq with key"][2].location(parser);
2628 CHECK(parser.location_contents(loc).begins_with("- nested first value"));
2629 CHECK(loc.line == 10u);
2630 CHECK(loc.col == 4u);
2631 loc = tree2["seq with key"][2][0].location(parser);
2632 CHECK(parser.location_contents(loc).begins_with("nested first value"));
2633 CHECK(loc.line == 10u);
2634 CHECK(loc.col == 6u);
2635 // MAP nested in SEQ: same as above: point to key
2636 loc = tree2["seq with key"][3].location(parser);
2637 CHECK(parser.location_contents(loc).begins_with("nested first: "));
2638 CHECK(loc.line == 13u);
2639 CHECK(loc.col == 4u);
2640 loc = tree2["seq with key"][3][0].location(parser);
2641 CHECK(parser.location_contents(loc).begins_with("nested first: "));
2642 CHECK(loc.line == 13u);
2643 CHECK(loc.col == 4u);
2644}
2645
2646
2647//-----------------------------------------------------------------------------
2648
2649/** shows how to programatically create trees
2650 * @see doc_tree
2651 * @see doc_node_classes
2652 * @see sample_create_tree_style()
2653 * */
2655{
2656 ryml::NodeRef doe;
2657 CHECK(doe.invalid()); // it's pointing at nowhere
2658
2659 ryml::Tree tree;
2660 ryml::NodeRef root = tree.rootref();
2661 root.set_map(); // mark root as a map
2662 doe = root["doe"];
2663 CHECK(!doe.invalid()); // it's now pointing at the tree
2664 CHECK(doe.is_seed()); // but the tree has nothing there, so this is only a seed
2665
2666 // set the value of the node
2667 const char a_deer[] = "a deer, a female deer";
2668 doe.set_val(a_deer);
2669 // now the node really exists in the tree, and this ref is no
2670 // longer a seed:
2671 CHECK(!doe.is_seed());
2672 // WATCHOUT for lifetimes:
2673 CHECK(doe.val().str == a_deer); // it is pointing at the initial string
2674 // If you need to avoid lifetime dependency, serialize the data:
2675 {
2676 std::string a_drop = "a drop of golden sun";
2677 // this will copy the string to the tree's arena:
2678 // (see the serialization samples below)
2679 root["ray"].set_serialized(a_drop);
2680 // and now you can modify the original string without changing
2681 // the tree:
2682 a_drop[0] = 'Z';
2683 a_drop[1] = 'Z';
2684 }
2685 CHECK(root["ray"].val() == "a drop of golden sun");
2686
2687 // etc.
2688 root["pi"].set_serialized(ryml::fmt::real(3.141592654, 5));
2689 root["xmas"].set_serialized(ryml::fmt::boolalpha(true));
2690 root["french-hens"].set_serialized(3);
2691 ryml::NodeRef calling_birds = root["calling-birds"];
2692 calling_birds.set_seq();
2693 calling_birds.append_child().set_val("huey");
2694 calling_birds.append_child().set_val("dewey");
2695 calling_birds.append_child().set_val("louie");
2696 calling_birds.append_child().set_val("fred");
2697 ryml::NodeRef xmas5 = root["xmas-fifth-day"];
2698 xmas5.set_map();
2699 xmas5["calling-birds"].set_val("four");
2700 xmas5["french-hens"].set_serialized(3);
2701 xmas5["golden-rings"].set_serialized(5);
2702 xmas5["partridges"].set_map();
2703 xmas5["partridges"]["count"].set_serialized(1);
2704 xmas5["partridges"]["location"].set_val("a pear tree");
2705 xmas5["turtle-doves"].set_val("two");
2706 root["cars"].set_val("GTO");
2707
2709 "doe: a deer, a female deer" "\n"
2710 "ray: a drop of golden sun" "\n"
2711 "pi: 3.14159" "\n"
2712 "xmas: true" "\n"
2713 "french-hens: 3" "\n"
2714 "calling-birds:" "\n"
2715 " - huey" "\n"
2716 " - dewey" "\n"
2717 " - louie" "\n"
2718 " - fred" "\n"
2719 "xmas-fifth-day:" "\n"
2720 " calling-birds: four" "\n"
2721 " french-hens: 3" "\n"
2722 " golden-rings: 5" "\n"
2723 " partridges:" "\n"
2724 " count: 1" "\n"
2725 " location: a pear tree" "\n"
2726 " turtle-doves: two" "\n"
2727 "cars: GTO" "\n"
2728 "");
2729
2730 // NOTE: it is good practice to set scalar styles when building
2731 // the tree. See the sample_create_tree_style() below.
2732}
2733
2734
2735//-----------------------------------------------------------------------------
2736
2737/** Shows how to set styles when building a tree: this may be needed
2738 * because you want to control the emitted YAML, but for scalars there
2739 * is also a performance reason to do this.
2740 *
2741 * @note You can **and should** set the style when creating a tree for
2742 * emitting later. This is because YAML has constraints on which
2743 * styles can be used for a particular scalar (eg, a plain scalar have
2744 * leading or trailing whitespace, or if in flow mode it cannot have
2745 * comma followed by space).
2746 *
2747 * @note When the scalar is not marked with an explicit style, the
2748 * ryml emitter adheres to these constraints by scanning each scalar
2749 * to choose a style for it. On the other hand, if the scalar is
2750 * marked with an explicit style, the emitter will honor that style,
2751 * and not do the scan.
2752 *
2753 * @note So explicitly setting the style for scalars
2754 * saves the emitter from having to scan each scalar while
2755 * emitting.
2756 *
2757 * @note For containers, setting the style does not offer an emit
2758 * performance improvement like with scalars. Nevertheless, you may
2759 * still find it useful to control the emitted YAML.
2760 *
2761 * Following this recommendation, ryml also explicitly sets the styles
2762 * while parsing.
2763 */
2765{
2766 ryml::Tree tree;
2767 ryml::NodeRef root = tree;
2768 root.set_map(ryml::BLOCK);
2769 // with .set_*() we should use explicit styles (because .set_*()
2770 // only takes csubstr arguments)
2771 root["not plain"].set_val(" with whitespace "); // no style set
2772 root["doe"].set_val("a deer, a female deer", ryml::VAL_PLAIN);
2773 root["ray"].set_val("a drop of golden sun", ryml::VAL_SQUO);
2774 root["me"].set_val("a name I call myself", ryml::VAL_DQUO);
2775 root["far"].set_val("a long long way to go", ryml::VAL_LITERAL);
2776 root["sow"].set_val("a needle pulling thread", ryml::VAL_FOLDED);
2777 root["seq"].set_seq(ryml::FLOW_SL|ryml::FLOW_SPC); // flow, single-line, with spaces after commas
2778 root["map1"].set_map(ryml::FLOW_ML1); // flow, multiline, 1 value per line
2779 root["mapn"].set_map(ryml::FLOW_MLN); // flow, multiline, N values per line, wrapped
2780 // likewise for all of set_serialized(), set_key(),
2781 // set_key_serialized(), save(), save_key(): all these accept the
2782 // style as an extra argument. But when serializing there's a
2783 // nice feature: ryml will automatically set the scalar style to
2784 // VAL_PLAIN / KEY_PLAIN when its type verifies std::is_arithmetic<T>.
2785 for(int i : {0, 1, 2, 3})
2786 {
2787 ryml::NodeRef childseq = root["seq"].append_child();
2788 ryml::NodeRef childmap1 = root["map1"].append_child();
2789 ryml::NodeRef childmapn = root["mapn"].append_child();
2790 // Note how we're NOT setting the style:
2791 childseq.set_serialized(i);
2792 childmap1.set_key_serialized(i + 1);
2793 childmapn.set_key_serialized(i + 1);
2794 childmap1.set_serialized((i + 1) * 10);
2795 childmapn.set_serialized((i + 1) * 10);
2796 // ... and yet ryml has set it to plain:
2797 CHECK(childseq.is_val_plain());
2798 CHECK(childmap1.is_key_plain());
2799 CHECK(childmapn.is_key_plain());
2800 CHECK(childmap1.is_val_plain());
2801 CHECK(childmapn.is_val_plain());
2802 }
2803 // let's see the styles now:
2805 // note how this is quoted, without having explicit style
2806 // set. That is because plain scalars cannot have
2807 // leading/trailing whitespace characters. This scalar did
2808 // not have a style set, so during emitting it was scanned
2809 // to determine its style, and so VAL_SQUO was chosen:
2810 "not plain: ' with whitespace '" "\n"
2811 // as for the rest, the style is honored, and the scalars
2812 // are not scanned by the emitter, resulting in a
2813 // performance increase:
2814 "doe: a deer, a female deer" "\n"
2815 "ray: 'a drop of golden sun'" "\n"
2816 "me: \"a name I call myself\"" "\n"
2817 "far: |-" "\n"
2818 " a long long way to go" "\n"
2819 "sow: >-" "\n"
2820 " a needle pulling thread" "\n"
2821 "seq: [0, 1, 2, 3]" "\n"
2822 "map1: {" "\n"
2823 " 1: 10," "\n"
2824 " 2: 20," "\n"
2825 " 3: 30," "\n"
2826 " 4: 40" "\n"
2827 " }" "\n"
2828 "mapn: {" "\n"
2829 " 1: 10,2: 20,3: 30,4: 40" "\n"
2830 " }" "\n"
2831 "");
2832 // Note that it would be an error to set a scalar style
2833 // incompatible with the scalar contents. ryml always honor the
2834 // node style, so it will blindly emit roundtrip-unstable YAML if
2835 // the style is incompatible.
2836 //
2837 // For example, this is incorrect because a plain scalar cannot
2838 // have leading or trailing whitespace:
2839 root["plain"].set_val(" with whitespace ", ryml::VAL_PLAIN); // incorrect.
2840 // note how the whitespace will be lost when parsed:
2841 CHECK(ryml::emitrs_yaml<std::string>(root["plain"]) ==
2842 "plain: with whitespace " "\n"
2843 "");
2844}
2845
2846
2847//-----------------------------------------------------------------------------
2848
2849/** demonstrates explicit and implicit interaction with the tree's
2850 * string arena. Note that ryml only holds strings in the tree's
2851 * nodes. */
2853{
2854 // mutable buffers are parsed in place:
2855 {
2856 char buf[] = "[a, b, c, d]";
2857 ryml::substr yml = buf;
2859 // notice the arena is empty:
2860 CHECK(tree.arena().empty());
2861 // and the tree is pointing at the original buffer:
2862 ryml::NodeRef root = tree.rootref();
2863 CHECK(root[0].val().is_sub(yml));
2864 CHECK(root[1].val().is_sub(yml));
2865 CHECK(root[2].val().is_sub(yml));
2866 CHECK(root[3].val().is_sub(yml));
2867 CHECK(yml.is_super(root[0].val()));
2868 CHECK(yml.is_super(root[1].val()));
2869 CHECK(yml.is_super(root[2].val()));
2870 CHECK(yml.is_super(root[3].val()));
2871 }
2872
2873 // when parsing immutable buffers, the buffer is first copied to the
2874 // tree's arena; the copy in the arena is then the buffer which is
2875 // actually parsed
2876 {
2877 ryml::csubstr yml = "[a, b, c, d]";
2879 // notice the buffer was copied to the arena:
2880 CHECK(tree.arena().data() != yml.data());
2881 CHECK(tree.arena() == yml);
2882 // and the tree is pointing at the arena instead of to the
2883 // original buffer:
2884 ryml::NodeRef root = tree.rootref();
2885 ryml::csubstr arena = tree.arena();
2886 CHECK(root[0].val().is_sub(arena));
2887 CHECK(root[1].val().is_sub(arena));
2888 CHECK(root[2].val().is_sub(arena));
2889 CHECK(root[3].val().is_sub(arena));
2890 CHECK(arena.is_super(root[0].val()));
2891 CHECK(arena.is_super(root[1].val()));
2892 CHECK(arena.is_super(root[2].val()));
2893 CHECK(arena.is_super(root[3].val()));
2894 }
2895
2896 // the arena is also used when the data is serialized to the tree.
2897 // first example: parse in place
2898 {
2899 char buf[] = "[a, b, c, d]"; // mutable
2900 ryml::substr yml = buf;
2902 // notice the arena is empty:
2903 CHECK(tree.arena().empty());
2904 ryml::NodeRef root = tree.rootref();
2905
2906 // serialize an integer, and mutate the tree
2907 CHECK(root[2].val() == "c");
2908 CHECK(root[2].val().is_sub(yml)); // val is first pointing at the buffer
2909 root[2].set_serialized(12345);
2910 CHECK(root[2].val() == "12345");
2911 CHECK(root[2].val().is_sub(tree.arena())); // now val is pointing at the arena
2912 // notice the serialized string was appended to the tree's arena:
2913 CHECK(tree.arena() == "12345");
2914
2915 // serialize an integer, and mutate the tree
2916 CHECK(root[3].val() == "d");
2917 CHECK(root[3].val().is_sub(yml)); // val is first pointing at the buffer
2918 root[3].set_serialized(67890);
2919 CHECK(root[3].val() == "67890");
2920 CHECK(root[3].val().is_sub(tree.arena())); // now val is pointing at the arena
2921 // notice the serialized string was appended to the tree's arena:
2922 CHECK(tree.arena() == "1234567890");
2923 }
2924 // the arena is also used when the data is serialized to string
2925 // second example: parse in arena
2926 {
2927 ryml::csubstr yml = "[a, b, c, d]"; // immutable
2929 // notice the buffer was copied to the arena:
2930 CHECK(tree.arena().data() != yml.data());
2931 CHECK(tree.arena() == yml);
2932 ryml::NodeRef root = tree.rootref();
2933
2934 // serialize an integer, and mutate the tree
2935 CHECK(root[2].val() == "c");
2936 root[2].set_serialized(12345); // serialize an integer
2937 CHECK(root[2].val() == "12345");
2938 // notice the serialized string was appended to the tree's arena:
2939 // notice also the previous values remain there.
2940 // RYML DOES NOT KEEP TRACK OF REFERENCES TO THE ARENA.
2941 CHECK(tree.arena() == "[a, b, c, d]12345");
2942 // old values: --------------^
2943
2944 // serialize an integer, and mutate the tree
2945 root[3].set_serialized(67890);
2946 CHECK(root[3].val() == "67890");
2947 // notice the serialized string was appended to the tree's arena:
2948 // notice also the previous values remain there.
2949 // RYML DOES NOT KEEP TRACK OF REFERENCES TO THE ARENA.
2950 CHECK(tree.arena() == "[a, b, c, d]1234567890");
2951 // old values: --------------^ ---^^^^^
2952 }
2953
2954 // to_arena(): directly serialize values to the arena:
2955 {
2956 ryml::Tree tree = ryml::parse_in_arena("{a: b}");
2957 ryml::csubstr c10 = tree.to_arena(10101010);
2958 CHECK(c10 == "10101010");
2959 CHECK(c10.is_sub(tree.arena()));
2960 CHECK(tree.arena() == "{a: b}10101010");
2961 CHECK(tree.key(1) == "a");
2962 CHECK(tree.val(1) == "b");
2963 tree.set_val(1, c10);
2964 CHECK(tree.val(1) == c10);
2965 // and you can also do it through a node:
2966 ryml::NodeRef root = tree.rootref();
2967 root["a"].set_serialized(2222);
2968 CHECK(root["a"].val() == "2222");
2969 CHECK(tree.arena() == "{a: b}101010102222");
2970 }
2971
2972 // copy_to_arena(): manually copy a string to the arena:
2973 {
2974 ryml::Tree tree = ryml::parse_in_arena("{a: b}");
2975 ryml::csubstr mystr = "Gosset Grande Reserve";
2976 ryml::csubstr copied = tree.copy_to_arena(mystr);
2977 CHECK(!copied.overlaps(mystr));
2978 CHECK(copied == mystr);
2979 CHECK(tree.arena() == "{a: b}Gosset Grande Reserve");
2980 }
2981
2982 // alloc_arena(): allocate a buffer from the arena:
2983 {
2984 ryml::Tree tree = ryml::parse_in_arena("{a: b}");
2985 ryml::csubstr mystr = "Gosset Grande Reserve";
2986 ryml::substr copied = tree.alloc_arena(mystr.size());
2987 CHECK(!copied.overlaps(mystr));
2988 memcpy(copied.str, mystr.str, mystr.len);
2989 CHECK(copied == mystr);
2990 CHECK(tree.arena() == "{a: b}Gosset Grande Reserve");
2991 }
2992
2993 // reserve_arena(): ensure the arena has a certain size to avoid reallocations
2994 {
2995 ryml::Tree tree = ryml::parse_in_arena("{a: b}");
2996 CHECK(tree.arena().size() == strlen("{a: b}"));
2997 tree.reserve_arena(100);
2998 CHECK(tree.arena_capacity() >= 100);
2999 CHECK(tree.arena().size() == strlen("{a: b}"));
3000 tree.to_arena(123456);
3001 CHECK(tree.arena().first(12) == "{a: b}123456");
3002 }
3003}
3004
3005
3006//-----------------------------------------------------------------------------
3007
3008/** ryml provides facilities for serializing and deserializing the C++
3009 fundamental types, including boolean and null values; this is
3010 provided by the several overloads in @ref doc_to_chars and @ref
3011 doc_from_chars. To add serialization for user scalar types (ie,
3012 those types that should be serialized as strings in leaf nodes),
3013 you just need to define the appropriate overloads of to_chars and
3014 from_chars for those types; see @ref sample_user_scalar_types for
3015 an example on how to achieve this, and see @ref doc_serialization
3016 for more information on serialization. */
3018{
3019 ryml::Tree tree;
3020 CHECK(tree.arena().empty());
3021 CHECK(tree.to_arena('a') == "a"); CHECK(tree.arena() == "a");
3022 CHECK(tree.to_arena("bcde") == "bcde"); CHECK(tree.arena() == "abcde");
3023 CHECK(tree.to_arena(unsigned(0)) == "0"); CHECK(tree.arena() == "abcde0");
3024 CHECK(tree.to_arena(int(1)) == "1"); CHECK(tree.arena() == "abcde01");
3025 CHECK(tree.to_arena(uint8_t(0)) == "0"); CHECK(tree.arena() == "abcde010");
3026 CHECK(tree.to_arena(uint16_t(1)) == "1"); CHECK(tree.arena() == "abcde0101");
3027 CHECK(tree.to_arena(uint32_t(2)) == "2"); CHECK(tree.arena() == "abcde01012");
3028 CHECK(tree.to_arena(uint64_t(3)) == "3"); CHECK(tree.arena() == "abcde010123");
3029 CHECK(tree.to_arena(int8_t( 4)) == "4"); CHECK(tree.arena() == "abcde0101234");
3030 CHECK(tree.to_arena(int8_t(-4)) == "-4"); CHECK(tree.arena() == "abcde0101234-4");
3031 CHECK(tree.to_arena(int16_t( 5)) == "5"); CHECK(tree.arena() == "abcde0101234-45");
3032 CHECK(tree.to_arena(int16_t(-5)) == "-5"); CHECK(tree.arena() == "abcde0101234-45-5");
3033 CHECK(tree.to_arena(int32_t( 6)) == "6"); CHECK(tree.arena() == "abcde0101234-45-56");
3034 CHECK(tree.to_arena(int32_t(-6)) == "-6"); CHECK(tree.arena() == "abcde0101234-45-56-6");
3035 CHECK(tree.to_arena(int64_t( 7)) == "7"); CHECK(tree.arena() == "abcde0101234-45-56-67");
3036 CHECK(tree.to_arena(int64_t(-7)) == "-7"); CHECK(tree.arena() == "abcde0101234-45-56-67-7");
3037 CHECK(tree.to_arena((void*)1) == "0x1"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x1");
3038 CHECK(tree.to_arena(float(0.124)) == "0.124"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.124");
3039 CHECK(tree.to_arena(double(0.234)) == "0.234"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.234");
3040
3041 // write boolean values - see also sample_formatting()
3042 CHECK(tree.to_arena(bool(true)) == "1"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.2341");
3043 CHECK(tree.to_arena(bool(false)) == "0"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410");
3044 CHECK(tree.to_arena(c4::fmt::boolalpha(true)) == "true"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410true");
3045 CHECK(tree.to_arena(c4::fmt::boolalpha(false)) == "false"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410truefalse");
3046
3047 // write special float values
3048 // see also sample_float_precision()
3049 const float fnan = std::numeric_limits<float >::quiet_NaN();
3050 const double dnan = std::numeric_limits<double>::quiet_NaN();
3051 const float finf = std::numeric_limits<float >::infinity();
3052 const double dinf = std::numeric_limits<double>::infinity();
3053 CHECK(tree.to_arena( finf) == ".inf"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410truefalse.inf");
3054 CHECK(tree.to_arena( dinf) == ".inf"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410truefalse.inf.inf");
3055 CHECK(tree.to_arena(-finf) == "-.inf"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410truefalse.inf.inf-.inf");
3056 CHECK(tree.to_arena(-dinf) == "-.inf"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410truefalse.inf.inf-.inf-.inf");
3057 CHECK(tree.to_arena( fnan) == ".nan"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410truefalse.inf.inf-.inf-.inf.nan");
3058 CHECK(tree.to_arena( dnan) == ".nan"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410truefalse.inf.inf-.inf-.inf.nan.nan");
3059
3060 // read special float values
3061 // see also sample_float_precision()
3062 C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH("-Wfloat-equal");
3063 tree = ryml::parse_in_arena(R"({ninf: -.inf, pinf: .inf, nan: .nan})");
3064 float f = 0.f;
3065 double d = 0.;
3066 CHECK(f == 0.f);
3067 CHECK(d == 0.);
3068 tree["ninf"].load(&f); CHECK(f == -finf);
3069 tree["ninf"].load(&d); CHECK(d == -dinf);
3070 tree["pinf"].load(&f); CHECK(f == finf);
3071 tree["pinf"].load(&d); CHECK(d == dinf);
3072 tree["nan" ].load(&f); CHECK(std::isnan(f));
3073 tree["nan" ].load(&d); CHECK(std::isnan(d));
3074 C4_SUPPRESS_WARNING_GCC_CLANG_POP
3075
3076 // value overflow detection:
3077 // (for integral types only)
3078 {
3079 // we will be detecting errors below, so we use this sample helper
3081 ryml::Tree t(err.callbacks()); // instantiate with the error-detecting callbacks
3082 // create a simple tree with an int value
3083 ryml::parse_in_arena(R"({val: 258})", &t);
3084 // by default, overflow is not detected:
3085 uint8_t valu8 = 0;
3086 int8_t vali8 = 0;
3087 ryml::ConstNodeRef n = t["val"];
3088 n.load(&valu8); CHECK(valu8 == 2); // not 257; it wrapped around
3089 n.load(&vali8); CHECK(vali8 == 2); // not 257; it wrapped around
3090 // ...but there are facilities to detect overflow
3094 // and there is a format helper
3095 CHECK(err.check_error_occurs([&]{
3096 n.load(ryml::fmt::overflow_checked(valu8)); // this will cause an error
3097 }));
3098 CHECK(err.check_error_occurs([&]{
3099 n.load(ryml::fmt::overflow_checked(vali8)); // this will cause an error
3100 }));
3101 }
3102}
3103
3104
3105//-----------------------------------------------------------------------------
3106
3107/** Shows how to deal with empty/null values. See also @ref
3108 * c4::yml::Tree::val_is_null */
3110{
3111 // reading empty/null values - see also sample_formatting()
3113 "plain:" "\n"
3114 "squoted: ''" "\n"
3115 "dquoted: \"\"" "\n"
3116 "literal: |" "\n"
3117 "folded: >" "\n"
3118 "all_null: [~, null, Null, NULL]" "\n"
3119 "non_null: [nULL, non_null, non null, null it is not]" "\n"
3120 "");
3121 // first, remember that .has_val() is a structural predicate
3122 // indicating the node is a leaf, and not a container.
3123 CHECK(tree["plain"].has_val()); // has a val, even if it's empty!
3124 CHECK(tree["squoted"].has_val());
3125 CHECK(tree["dquoted"].has_val());
3126 CHECK(tree["literal"].has_val());
3127 CHECK(tree["folded"].has_val());
3128 CHECK( ! tree["all_null"].has_val());
3129 CHECK( ! tree["non_null"].has_val());
3130 // In essence, has_val() is the logical opposite of is_container()
3131 CHECK( ! tree["plain"].is_container());
3132 CHECK( ! tree["squoted"].is_container());
3133 CHECK( ! tree["dquoted"].is_container());
3134 CHECK( ! tree["literal"].is_container());
3135 CHECK( ! tree["folded"].is_container());
3136 CHECK(tree["all_null"].is_container());
3137 CHECK(tree["non_null"].is_container());
3138 //
3139 // Right. How about the contents of each val?
3140 //
3141 // all of these scalars have zero-length:
3142 CHECK(tree["plain"].val().len == 0);
3143 CHECK(tree["squoted"].val().len == 0);
3144 CHECK(tree["dquoted"].val().len == 0);
3145 CHECK(tree["literal"].val().len == 0);
3146 CHECK(tree["folded"].val().len == 0);
3147 // but only the empty scalar has null string:
3148 CHECK(tree["plain"].val().str == nullptr);
3149 CHECK(tree["squoted"].val().str != nullptr);
3150 CHECK(tree["dquoted"].val().str != nullptr);
3151 CHECK(tree["literal"].val().str != nullptr);
3152 CHECK(tree["folded"].val().str != nullptr);
3153 // likewise, scalar comparison to nullptr has the same results:
3154 // (remember that .val() gives you the scalar value, node must
3155 // have a val, ie must be a leaf node, not a container)
3156 CHECK(tree["plain"].val() == nullptr);
3157 CHECK(tree["squoted"].val() != nullptr);
3158 CHECK(tree["dquoted"].val() != nullptr);
3159 CHECK(tree["literal"].val() != nullptr);
3160 CHECK(tree["folded"].val() != nullptr);
3161 // the tree and node classes provide the corresponding predicate
3162 // functions .key_is_null() and .val_is_null().
3163 // (note that these functions have the same preconditions as .val(),
3164 // because they need get the val to look into its contents)
3165 CHECK(tree["plain"].val_is_null());
3166 CHECK( ! tree["squoted"].val_is_null());
3167 CHECK( ! tree["dquoted"].val_is_null());
3168 CHECK( ! tree["literal"].val_is_null());
3169 CHECK( ! tree["folded"].val_is_null());
3170 // matching to null is case-sensitive. only the cases shown here
3171 // match to null:
3172 for(ryml::ConstNodeRef child : tree["all_null"].children())
3173 {
3174 CHECK(child.val() != nullptr); // it is pointing at a string, so it is not nullptr!
3175 CHECK(child.val_is_null());
3176 }
3177 for(ryml::ConstNodeRef child : tree["non_null"].children())
3178 {
3179 CHECK(child.val() != nullptr);
3180 CHECK( ! child.val_is_null());
3181 }
3182 //
3183 //
3184 // Because the meaning of null/~/empty will vary from application
3185 // to application, ryml makes no assumption on what should be
3186 // serialized as null. It leaves this decision to the user. But
3187 // it also provides the proper toolbox for the user to implement
3188 // its intended solution.
3189 //
3190 // writing/disambiguating null values:
3191 ryml::csubstr null = {};
3192 ryml::csubstr nonnull = "";
3193 ryml::csubstr strnull = "null";
3194 ryml::csubstr tilde = "~";
3195 CHECK(null .len == 0); CHECK(null .str == nullptr); CHECK(null == nullptr);
3196 CHECK(nonnull.len == 0); CHECK(nonnull.str != nullptr); CHECK(nonnull != nullptr);
3197 CHECK(strnull.len != 0); CHECK(strnull.str != nullptr); CHECK(strnull != nullptr);
3198 CHECK(tilde .len != 0); CHECK(tilde .str != nullptr); CHECK(tilde != nullptr);
3199 tree.clear();
3200 tree.clear_arena();
3201 tree.rootref().set_map();
3202 // serializes as an empty plain scalar:
3203 tree["empty_null"].set_serialized(null); CHECK(tree.arena() == "");
3204 // serializes as an empty quoted scalar:
3205 tree["empty_nonnull"].set_serialized(nonnull); CHECK(tree.arena() == "");
3206 // serializes as the normal 'null' string:
3207 tree["str_null"].set_serialized(strnull); CHECK(tree.arena() == "null");
3208 // serializes as the normal '~' string:
3209 tree["str_tilde"].set_serialized(tilde); CHECK(tree.arena() == "null~");
3210 // this is the resulting yaml:
3212 "empty_null: " "\n"
3213 "empty_nonnull: ''" "\n"
3214 "str_null: null" "\n"
3215 "str_tilde: ~" "\n"
3216 "");
3217 // To enforce a particular concept of what is a null string, you
3218 // can use the appropriate condition based on pointer nullity or
3219 // other appropriate criteria.
3220 //
3221 // As an example, proper comparison to nullptr:
3222 auto null_if_nullptr = [](ryml::csubstr s) {
3223 return s.str == nullptr ? "null" : s;
3224 };
3225 tree["empty_null"].set_serialized(null_if_nullptr(null));
3226 tree["empty_nonnull"].set_serialized(null_if_nullptr(nonnull));
3227 tree["str_null"].set_serialized(null_if_nullptr(strnull));
3228 tree["str_tilde"].set_serialized(null_if_nullptr(tilde));
3229 // this is the resulting yaml:
3231 "empty_null: null" "\n"
3232 "empty_nonnull: ''" "\n"
3233 "str_null: null" "\n"
3234 "str_tilde: ~" "\n"
3235 "");
3236 //
3237 // As another example, nullity check based on the YAML nullity
3238 // predicate:
3239 auto null_if_predicate = [](ryml::csubstr s) {
3240 return ryml::scalar_is_null(s) ? "null" : s;
3241 };
3242 tree["empty_null"].set_serialized(null_if_predicate(null));
3243 tree["empty_nonnull"].set_serialized(null_if_predicate(nonnull));
3244 tree["str_null"].set_serialized(null_if_predicate(strnull));
3245 tree["str_tilde"].set_serialized(null_if_predicate(tilde));
3246 // this is the resulting yaml:
3248 "empty_null: null" "\n"
3249 "empty_nonnull: ''" "\n"
3250 "str_null: null" "\n"
3251 "str_tilde: null" "\n"
3252 "");
3253 //
3254 // As another example, nullity check based on the YAML nullity
3255 // predicate, but returning "~" to simbolize nullity:
3256 auto tilde_if_predicate = [](ryml::csubstr s) {
3257 return ryml::scalar_is_null(s) ? "~" : s;
3258 };
3259 tree["empty_null"].set_serialized(tilde_if_predicate(null));
3260 tree["empty_nonnull"].set_serialized(tilde_if_predicate(nonnull));
3261 tree["str_null"].set_serialized(tilde_if_predicate(strnull));
3262 tree["str_tilde"].set_serialized(tilde_if_predicate(tilde));
3263 // this is the resulting yaml:
3265 "empty_null: ~" "\n"
3266 "empty_nonnull: ''" "\n"
3267 "str_null: ~" "\n"
3268 "str_tilde: ~" "\n"
3269 "");
3270}
3271
3272
3273//-----------------------------------------------------------------------------
3274
3275/** ryml provides facilities for formatting/deformatting (imported
3276 * from c4core into the ryml namespace). See @ref doc_format_utils
3277 * . These functions are very useful to serialize and deserialize
3278 * scalar types; see @ref doc_serialization .
3279 */
3281{
3282 // format(), format_sub(), formatrs(): format arguments
3283 {
3284 char buf_[256] = {};
3285 ryml::substr buf = buf_;
3286 size_t size = ryml::format(buf, "a={} foo {} {} bar {}", 0.1, 10, 11, 12);
3287 CHECK(size == strlen("a=0.1 foo 10 11 bar 12"));
3288 CHECK(buf.first(size) == "a=0.1 foo 10 11 bar 12");
3289 // it is safe to call on an empty buffer:
3290 // returns the size needed for the result, and no overflow occurs:
3291 size = ryml::format({} , "a={} foo {} {} bar {}", "this_is_a", 10, 11, 12);
3292 CHECK(size == ryml::format(buf, "a={} foo {} {} bar {}", "this_is_a", 10, 11, 12));
3293 CHECK(size == strlen("a=this_is_a foo 10 11 bar 12"));
3294 // it is also safe to call on an insufficient buffer:
3295 char smallbuf[8] = {};
3296 size = ryml::format(smallbuf, "{} is too large {}", "this", "for the buffer");
3297 CHECK(size == strlen("this is too large for the buffer"));
3298 // ... and the result is truncated at the buffer size:
3299 CHECK(ryml::substr(smallbuf, sizeof(smallbuf)) == "this is\0");
3300
3301 // format_sub() directly returns the written string:
3302 ryml::csubstr result = ryml::format_sub(buf, "b={}, damn it.", 1);
3303 CHECK(result == "b=1, damn it.");
3304 CHECK(result.is_sub(buf));
3305
3306 // formatrs() means FORMAT & ReSize:
3307 //
3308 // Instead of a substr, it receives any owning linear char container
3309 // for which to_substr() is defined (using ADL).
3310 // <ryml_std.hpp> has to_substr() definitions for std::string and
3311 // std::vector<char>.
3312 //
3313 // formatrs() starts by calling format(), and if needed, resizes the container
3314 // and calls format() again.
3315 //
3316 // Note that unless the container is previously sized, this
3317 // may cause an allocation, which will make your code slower.
3318 // Make sure to call .reserve() on the container for real
3319 // production code.
3320 std::string sbuf;
3321 ryml::formatrs(&sbuf, "and c={} seems about right", 2);
3322 CHECK(sbuf == "and c=2 seems about right");
3323 std::vector<char> vbuf; // works with any linear char container
3324 ryml::formatrs(&vbuf, "and c={} seems about right", 2);
3325 CHECK(sbuf == "and c=2 seems about right");
3326 // with formatrs() it is also possible to append:
3327 ryml::formatrs_append(&sbuf, ", and finally d={} - done", 3);
3328 CHECK(sbuf == "and c=2 seems about right, and finally d=3 - done");
3329 }
3330
3331 // unformat(): read arguments - opposite of format()
3332 {
3333 char buf_[256];
3334
3335 int a = 0, b = 1, c = 2;
3336 ryml::csubstr result = ryml::format_sub(buf_, "{} and {} and {}", a, b, c);
3337 CHECK(result == "0 and 1 and 2");
3338 int aa = -1, bb = -2, cc = -3;
3339 size_t num_characters = ryml::unformat(result, "{} and {} and {}", aa, bb, cc);
3340 CHECK(num_characters != ryml::csubstr::npos); // if a conversion fails, returns ryml::csubstr::npos
3341 CHECK(num_characters == result.size());
3342 CHECK(aa == a);
3343 CHECK(bb == b);
3344 CHECK(cc == c);
3345
3346 result = ryml::format_sub(buf_, "{} and {} and {}", 10, 20, 30);
3347 CHECK(result == "10 and 20 and 30");
3348 num_characters = ryml::unformat(result, "{} and {} and {}", aa, bb, cc);
3349 CHECK(num_characters != ryml::csubstr::npos); // if a conversion fails, returns ryml::csubstr::npos
3350 CHECK(num_characters == result.size());
3351 CHECK(aa == 10);
3352 CHECK(bb == 20);
3353 CHECK(cc == 30);
3354 }
3355
3356 // cat(), cat_sub(), catrs(): concatenate arguments
3357 {
3358 char buf_[256] = {};
3359 ryml::substr buf = buf_;
3360 size_t size = ryml::cat(buf, "a=", 0.1, "foo", 10, 11, "bar", 12);
3361 CHECK(size == strlen("a=0.1foo1011bar12"));
3362 CHECK(buf.first(size) == "a=0.1foo1011bar12");
3363 // it is safe to call on an empty buffer:
3364 // returns the size needed for the result, and no overflow occurs:
3365 CHECK(ryml::cat({}, "a=", 0) == 3);
3366 // it is also safe to call on an insufficient buffer:
3367 char smallbuf[8] = {};
3368 size = ryml::cat(smallbuf, "this", " is too large ", "for the buffer");
3369 CHECK(size == strlen("this is too large for the buffer"));
3370 // ... and the result is truncated at the buffer size:
3371 CHECK(ryml::substr(smallbuf, sizeof(smallbuf)) == "this is\0");
3372
3373 // cat_sub() directly returns the written string:
3374 ryml::csubstr result = ryml::cat_sub(buf, "b=", 1, ", damn it.");
3375 CHECK(result == "b=1, damn it.");
3376 CHECK(result.is_sub(buf));
3377
3378 // catrs() means CAT & ReSize:
3379 //
3380 // Instead of a substr, it receives any owning linear char container
3381 // for which to_substr() is defined (using ADL).
3382 // <ryml_std.hpp> has to_substr() definitions for std::string and
3383 // std::vector<char>.
3384 //
3385 // catrs() starts by calling cat(), and if needed, resizes the container
3386 // and calls cat() again.
3387 //
3388 // Note that unless the container is previously sized, this
3389 // may cause an allocation, which will make your code slower.
3390 // Make sure to call .reserve() on the container for real
3391 // production code.
3392 std::string sbuf;
3393 ryml::catrs(&sbuf, "and c=", 2, " seems about right");
3394 CHECK(sbuf == "and c=2 seems about right");
3395 std::vector<char> vbuf; // works with any linear char container
3396 ryml::catrs(&vbuf, "and c=", 2, " seems about right");
3397 CHECK(sbuf == "and c=2 seems about right");
3398 // with catrs() it is also possible to append:
3399 ryml::catrs_append(&sbuf, ", and finally d=", 3, " - done");
3400 CHECK(sbuf == "and c=2 seems about right, and finally d=3 - done");
3401 }
3402
3403 // uncat(): read arguments - opposite of cat()
3404 {
3405 char buf_[256];
3406
3407 int a = 0, b = 1, c = 2;
3408 ryml::csubstr result = ryml::cat_sub(buf_, a, ' ', b, ' ', c);
3409 CHECK(result == "0 1 2");
3410 int aa = -1, bb = -2, cc = -3;
3411 char sep1 = 'a', sep2 = 'b';
3412 size_t num_characters = ryml::uncat(result, aa, sep1, bb, sep2, cc);
3413 CHECK(num_characters == result.size());
3414 CHECK(aa == a);
3415 CHECK(bb == b);
3416 CHECK(cc == c);
3417 CHECK(sep1 == ' ');
3418 CHECK(sep2 == ' ');
3419
3420 result = ryml::cat_sub(buf_, 10, ' ', 20, ' ', 30);
3421 CHECK(result == "10 20 30");
3422 num_characters = ryml::uncat(result, aa, sep1, bb, sep2, cc);
3423 CHECK(num_characters == result.size());
3424 CHECK(aa == 10);
3425 CHECK(bb == 20);
3426 CHECK(cc == 30);
3427 CHECK(sep1 == ' ');
3428 CHECK(sep2 == ' ');
3429 }
3430
3431 // catsep(), catsep_sub(), catseprs(): concatenate arguments, with a separator
3432 {
3433 char buf_[256] = {};
3434 ryml::substr buf = buf_;
3435 // use ' ' as a separator
3436 size_t size = ryml::catsep(buf, ' ', "a=", 0, "b=", 1, "c=", 2, 45, 67);
3437 CHECK(buf.first(size) == "a= 0 b= 1 c= 2 45 67");
3438 // any separator may be used
3439 // use " and " as a separator
3440 size = ryml::catsep(buf, " and ", "a=0", "b=1", "c=2", 45, 67);
3441 CHECK(buf.first(size) == "a=0 and b=1 and c=2 and 45 and 67");
3442 // use " ... " as a separator
3443 size = ryml::catsep(buf, " ... ", "a=0", "b=1", "c=2", 45, 67);
3444 CHECK(buf.first(size) == "a=0 ... b=1 ... c=2 ... 45 ... 67");
3445 // use '/' as a separator
3446 size = ryml::catsep(buf, '/', "a=", 0, "b=", 1, "c=", 2, 45, 67);
3447 CHECK(buf.first(size) == "a=/0/b=/1/c=/2/45/67");
3448 // use 888 as a separator
3449 size = ryml::catsep(buf, 888, "a=0", "b=1", "c=2", 45, 67);
3450 CHECK(buf.first(size) == "a=0888b=1888c=28884588867");
3451
3452 // it is safe to call on an empty buffer:
3453 // returns the size needed for the result, and no overflow occurs:
3454 CHECK(size == ryml::catsep({}, 888, "a=0", "b=1", "c=2", 45, 67));
3455 // it is also safe to call on an insufficient buffer:
3456 char smallbuf[8] = {};
3457 CHECK(size == ryml::catsep(smallbuf, 888, "a=0", "b=1", "c=2", 45, 67));
3458 CHECK(size == strlen("a=0888b=1888c=28884588867"));
3459 // ... and the result is truncated:
3460 CHECK(ryml::substr(smallbuf, sizeof(smallbuf)) == "a=0888b\0");
3461
3462 // catsep_sub() directly returns the written substr:
3463 ryml::csubstr result = ryml::catsep_sub(buf, " and ", "a=0", "b=1", "c=2", 45, 67);
3464 CHECK(result == "a=0 and b=1 and c=2 and 45 and 67");
3465 CHECK(result.is_sub(buf));
3466
3467 // catseprs() means CATSEP & ReSize:
3468 //
3469 // Instead of a substr, it receives any owning linear char container
3470 // for which to_substr() is defined (using ADL).
3471 // <ryml_std.hpp> has to_substr() definitions for std::string and
3472 // std::vector<char>.
3473 //
3474 // catseprs() starts by calling catsep(), and if needed, resizes the container
3475 // and calls catsep() again.
3476 //
3477 // Note that unless the container is previously sized, this
3478 // may cause an allocation, which will make your code slower.
3479 // Make sure to call .reserve() on the container for real
3480 // production code.
3481 std::string sbuf;
3482 ryml::catseprs(&sbuf, " and ", "a=0", "b=1", "c=2", 45, 67);
3483 CHECK(sbuf == "a=0 and b=1 and c=2 and 45 and 67");
3484 std::vector<char> vbuf; // works with any linear char container
3485 ryml::catseprs(&vbuf, " and ", "a=0", "b=1", "c=2", 45, 67);
3486 CHECK(ryml::to_csubstr(vbuf) == "a=0 and b=1 and c=2 and 45 and 67");
3487
3488 // with catseprs() it is also possible to append:
3489 ryml::catseprs_append(&sbuf, " well ", " --- a=0", "b=11", "c=12", 145, 167);
3490 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");
3491 }
3492
3493 // uncatsep(): read arguments with a separator - opposite of catsep()
3494 {
3495 char buf_[256] = {};
3496
3497 int a = 0, b = 1, c = 2;
3498 ryml::csubstr result = ryml::catsep_sub(buf_, ' ', a, b, c);
3499 CHECK(result == "0 1 2");
3500 int aa = -1, bb = -2, cc = -3;
3501 size_t num_characters = ryml::uncatsep(result, " ", aa, bb, cc);
3502 CHECK(num_characters == result.size());
3503 CHECK(aa == a);
3504 CHECK(bb == b);
3505 CHECK(cc == c);
3506
3507 result = ryml::catsep_sub(buf_, "--", 10, 20, 30);
3508 CHECK(result == "10--20--30");
3509 num_characters = ryml::uncatsep(result, "--", aa, bb, cc);
3510 CHECK(num_characters == result.size());
3511 CHECK(aa == 10);
3512 CHECK(bb == 20);
3513 CHECK(cc == 30);
3514 }
3515
3516 // formatting individual arguments
3517 {
3518 using namespace ryml; // all the symbols below are in the ryml namespace.
3519 char buf_[256] = {}; // all the results below are written in this buffer
3520 substr buf = buf_;
3521 // --------------------------------------
3522 // fmt::boolalpha(): format as true/false
3523 // --------------------------------------
3524 // just as with std streams, printing a bool will output the integer value:
3525 CHECK("0" == cat_sub(buf, false));
3526 CHECK("1" == cat_sub(buf, true));
3527 // to force a "true"/"false", use fmt::boolalpha:
3528 CHECK("false" == cat_sub(buf, fmt::boolalpha(false)));
3529 CHECK("true" == cat_sub(buf, fmt::boolalpha(true)));
3530
3531 // ---------------------------------
3532 // fmt::hex(): format as hexadecimal
3533 // ---------------------------------
3534 CHECK("0xff" == cat_sub(buf, fmt::hex(255)));
3535 CHECK("0x100" == cat_sub(buf, fmt::hex(256)));
3536 CHECK("-0xff" == cat_sub(buf, fmt::hex(-255)));
3537 CHECK("-0x100" == cat_sub(buf, fmt::hex(-256)));
3538 CHECK("3735928559" == cat_sub(buf, UINT32_C(0xdeadbeef)));
3539 CHECK("0xdeadbeef" == cat_sub(buf, fmt::hex(UINT32_C(0xdeadbeef))));
3540 // ----------------------------
3541 // fmt::bin(): format as binary
3542 // ----------------------------
3543 CHECK("0b1000" == cat_sub(buf, fmt::bin(8)));
3544 CHECK("0b1001" == cat_sub(buf, fmt::bin(9)));
3545 CHECK("0b10001" == cat_sub(buf, fmt::bin(17)));
3546 CHECK("0b11001" == cat_sub(buf, fmt::bin(25)));
3547 CHECK("-0b1000" == cat_sub(buf, fmt::bin(-8)));
3548 CHECK("-0b1001" == cat_sub(buf, fmt::bin(-9)));
3549 CHECK("-0b10001" == cat_sub(buf, fmt::bin(-17)));
3550 CHECK("-0b11001" == cat_sub(buf, fmt::bin(-25)));
3551 // ---------------------------
3552 // fmt::bin(): format as octal
3553 // ---------------------------
3554 CHECK("0o77" == cat_sub(buf, fmt::oct(63)));
3555 CHECK("0o100" == cat_sub(buf, fmt::oct(64)));
3556 CHECK("0o377" == cat_sub(buf, fmt::oct(255)));
3557 CHECK("0o400" == cat_sub(buf, fmt::oct(256)));
3558 CHECK("0o1000" == cat_sub(buf, fmt::oct(512)));
3559 CHECK("-0o77" == cat_sub(buf, fmt::oct(-63)));
3560 CHECK("-0o100" == cat_sub(buf, fmt::oct(-64)));
3561 CHECK("-0o377" == cat_sub(buf, fmt::oct(-255)));
3562 CHECK("-0o400" == cat_sub(buf, fmt::oct(-256)));
3563 CHECK("-0o1000" == cat_sub(buf, fmt::oct(-512)));
3564 // ---------------------------
3565 // fmt::zpad(): pad with zeros
3566 // ---------------------------
3567 CHECK("000063" == cat_sub(buf, fmt::zpad(63, 6)));
3568 CHECK( "00063" == cat_sub(buf, fmt::zpad(63, 5)));
3569 CHECK( "0063" == cat_sub(buf, fmt::zpad(63, 4)));
3570 CHECK( "063" == cat_sub(buf, fmt::zpad(63, 3)));
3571 CHECK( "63" == cat_sub(buf, fmt::zpad(63, 2)));
3572 CHECK( "63" == cat_sub(buf, fmt::zpad(63, 1))); // will never trim the result
3573 CHECK( "63" == cat_sub(buf, fmt::zpad(63, 0))); // will never trim the result
3574 CHECK("0x00003f" == cat_sub(buf, fmt::zpad(fmt::hex(63), 6)));
3575 CHECK("0o000077" == cat_sub(buf, fmt::zpad(fmt::oct(63), 6)));
3576 CHECK("0b00011001" == cat_sub(buf, fmt::zpad(fmt::bin(25), 8)));
3577 // ------------------------------------------------
3578 // fmt::left(): align left with a given field width
3579 // ------------------------------------------------
3580 CHECK("63 " == cat_sub(buf, fmt::left(63, 6)));
3581 CHECK("63 " == cat_sub(buf, fmt::left(63, 5)));
3582 CHECK("63 " == cat_sub(buf, fmt::left(63, 4)));
3583 CHECK("63 " == cat_sub(buf, fmt::left(63, 3)));
3584 CHECK("63" == cat_sub(buf, fmt::left(63, 2)));
3585 CHECK("63" == cat_sub(buf, fmt::left(63, 1))); // will never trim the result
3586 CHECK("63" == cat_sub(buf, fmt::left(63, 0))); // will never trim the result
3587 // the fill character can be specified (defaults to ' '):
3588 CHECK("63----" == cat_sub(buf, fmt::left(63, 6, '-')));
3589 CHECK("63++++" == cat_sub(buf, fmt::left(63, 6, '+')));
3590 CHECK("63////" == cat_sub(buf, fmt::left(63, 6, '/')));
3591 CHECK("630000" == cat_sub(buf, fmt::left(63, 6, '0')));
3592 CHECK("63@@@@" == cat_sub(buf, fmt::left(63, 6, '@')));
3593 CHECK("0x003f " == cat_sub(buf, fmt::left(fmt::zpad(fmt::hex(63), 4), 10)));
3594 // --------------------------------------------------
3595 // fmt::right(): align right with a given field width
3596 // --------------------------------------------------
3597 CHECK(" 63" == cat_sub(buf, fmt::right(63, 6)));
3598 CHECK(" 63" == cat_sub(buf, fmt::right(63, 5)));
3599 CHECK(" 63" == cat_sub(buf, fmt::right(63, 4)));
3600 CHECK(" 63" == cat_sub(buf, fmt::right(63, 3)));
3601 CHECK("63" == cat_sub(buf, fmt::right(63, 2)));
3602 CHECK("63" == cat_sub(buf, fmt::right(63, 1))); // will never trim the result
3603 CHECK("63" == cat_sub(buf, fmt::right(63, 0))); // will never trim the result
3604 // the fill character can be specified (defaults to ' '):
3605 CHECK("----63" == cat_sub(buf, fmt::right(63, 6, '-')));
3606 CHECK("++++63" == cat_sub(buf, fmt::right(63, 6, '+')));
3607 CHECK("////63" == cat_sub(buf, fmt::right(63, 6, '/')));
3608 CHECK("000063" == cat_sub(buf, fmt::right(63, 6, '0')));
3609 CHECK("@@@@63" == cat_sub(buf, fmt::right(63, 6, '@')));
3610 CHECK(" 0x003f" == cat_sub(buf, fmt::right(fmt::zpad(fmt::hex(63), 4), 10)));
3611
3612 // ------------------------------------------
3613 // fmt::real(): format floating point numbers
3614 // ------------------------------------------
3615 // see also sample_float_precision()
3616 CHECK("0" == cat_sub(buf, fmt::real(0.01f, 0)));
3617 CHECK("0.0" == cat_sub(buf, fmt::real(0.01f, 1)));
3618 CHECK("0.01" == cat_sub(buf, fmt::real(0.01f, 2)));
3619 CHECK("0.010" == cat_sub(buf, fmt::real(0.01f, 3)));
3620 CHECK("0.0100" == cat_sub(buf, fmt::real(0.01f, 4)));
3621 CHECK("0.01000" == cat_sub(buf, fmt::real(0.01f, 5)));
3622 CHECK("1" == cat_sub(buf, fmt::real(1.01f, 0)));
3623 CHECK("1.0" == cat_sub(buf, fmt::real(1.01f, 1)));
3624 CHECK("1.01" == cat_sub(buf, fmt::real(1.01f, 2)));
3625 CHECK("1.010" == cat_sub(buf, fmt::real(1.01f, 3)));
3626 CHECK("1.0100" == cat_sub(buf, fmt::real(1.01f, 4)));
3627 CHECK("1.01000" == cat_sub(buf, fmt::real(1.01f, 5)));
3628 CHECK("1" == cat_sub(buf, fmt::real(1.234234234, 0)));
3629 CHECK("1.2" == cat_sub(buf, fmt::real(1.234234234, 1)));
3630 CHECK("1.23" == cat_sub(buf, fmt::real(1.234234234, 2)));
3631 CHECK("1.234" == cat_sub(buf, fmt::real(1.234234234, 3)));
3632 CHECK("1.2342" == cat_sub(buf, fmt::real(1.234234234, 4)));
3633 CHECK("1.23423" == cat_sub(buf, fmt::real(1.234234234, 5)));
3634 CHECK("1000000.00000" == cat_sub(buf, fmt::real(1000000.000000000, 5)));
3635 CHECK("1234234.23423" == cat_sub(buf, fmt::real(1234234.234234234, 5)));
3636 // AKA %f
3637 CHECK("1000000.00000" == cat_sub(buf, fmt::real(1000000.000000000, 5, FTOA_FLOAT))); // AKA %f, same as above
3638 CHECK("1234234.23423" == cat_sub(buf, fmt::real(1234234.234234234, 5, FTOA_FLOAT))); // AKA %f
3639 CHECK("1234234.2342" == cat_sub(buf, fmt::real(1234234.234234234, 4, FTOA_FLOAT))); // AKA %f
3640 CHECK("1234234.234" == cat_sub(buf, fmt::real(1234234.234234234, 3, FTOA_FLOAT))); // AKA %f
3641 CHECK("1234234.23" == cat_sub(buf, fmt::real(1234234.234234234, 2, FTOA_FLOAT))); // AKA %f
3642 // AKA %e
3643 CHECK("1.00000e+06" == cat_sub(buf, fmt::real(1000000.000000000, 5, FTOA_SCIENT))); // AKA %e
3644 CHECK("1.23423e+06" == cat_sub(buf, fmt::real(1234234.234234234, 5, FTOA_SCIENT))); // AKA %e
3645 CHECK("1.2342e+06" == cat_sub(buf, fmt::real(1234234.234234234, 4, FTOA_SCIENT))); // AKA %e
3646 CHECK("1.234e+06" == cat_sub(buf, fmt::real(1234234.234234234, 3, FTOA_SCIENT))); // AKA %e
3647 CHECK("1.23e+06" == cat_sub(buf, fmt::real(1234234.234234234, 2, FTOA_SCIENT))); // AKA %e
3648 // AKA %g
3649 CHECK("1e+06" == cat_sub(buf, fmt::real(1000000.000000000, 5, FTOA_FLEX))); // AKA %g
3650 CHECK("1.2342e+06" == cat_sub(buf, fmt::real(1234234.234234234, 5, FTOA_FLEX))); // AKA %g
3651 CHECK("1.234e+06" == cat_sub(buf, fmt::real(1234234.234234234, 4, FTOA_FLEX))); // AKA %g
3652 CHECK("1.23e+06" == cat_sub(buf, fmt::real(1234234.234234234, 3, FTOA_FLEX))); // AKA %g
3653 CHECK("1.2e+06" == cat_sub(buf, fmt::real(1234234.234234234, 2, FTOA_FLEX))); // AKA %g
3654 // FTOA_HEXA: AKA %a (hexadecimal formatting of floats)
3655 CHECK("0x1.e8480p+19" == cat_sub(buf, fmt::real(1000000.000000000, 5, FTOA_HEXA))); // AKA %a
3656 CHECK("0x1.2d53ap+20" == cat_sub(buf, fmt::real(1234234.234234234, 5, FTOA_HEXA))); // AKA %a
3657
3658 // --------------------------------------------------------------
3659 // fmt::raw(): dump data in raw (binary) machine format (respecting alignment)
3660 // --------------------------------------------------------------
3661 {
3662 C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH("-Wcast-align") // we're casting the values directly, so alignment is strictly respected.
3663 const uint32_t payload[] = {10, 20, 30, 40, UINT32_C(0xdeadbeef)};
3664 // (package payload as a substr, for comparison only)
3665 csubstr expected = csubstr((const char *)payload, sizeof(payload));
3666 csubstr actual = cat_sub(buf, fmt::raw(payload));
3667 CHECK(!actual.overlaps(expected));
3668 CHECK(0 == memcmp(expected.str, actual.str, expected.len));
3669 // also possible with variables:
3670 for(const uint32_t value : payload)
3671 {
3672 // (package payload as a substr, for comparison only)
3673 expected = csubstr((const char *)&value, sizeof(value));
3674 actual = cat_sub(buf, fmt::raw(value));
3675 CHECK(actual.size() == sizeof(uint32_t));
3676 CHECK(!actual.overlaps(expected));
3677 CHECK(0 == memcmp(expected.str, actual.str, expected.len));
3678 // with non-const data, fmt::craw() may be needed for disambiguation:
3679 actual = cat_sub(buf, fmt::craw(value));
3680 CHECK(actual.size() == sizeof(uint32_t));
3681 CHECK(!actual.overlaps(expected));
3682 CHECK(0 == memcmp(expected.str, actual.str, expected.len));
3683 //
3684 // read back:
3685 uint32_t result = 0;
3686 auto reader = fmt::raw(result);
3687 CHECK(uncat(actual, reader));
3688 // and compare:
3689 // (vs2017/release/32bit does not reload result from cache, so force it)
3690 CHECK(result == value); // roundtrip completed successfully
3691 }
3692 C4_SUPPRESS_WARNING_GCC_CLANG_POP
3693 }
3694
3695 // -------------------------
3696 // fmt::base64(): see below!
3697 // -------------------------
3698 }
3699}
3700
3701
3702//-----------------------------------------------------------------------------
3703
3704/** demonstrates how to read and write base64-encoded blobs.
3705 * @see @ref doc_base64 */
3707{
3708 // let's start by creating a tree with base64 vals and keys
3709 ryml::Tree tree;
3710 tree.rootref().set_map();
3711 struct text_and_base64 { ryml::csubstr text, base64; };
3712 text_and_base64 cases[] = {
3713 {{"Hello, World!"}, {"SGVsbG8sIFdvcmxkIQ=="}},
3714 {{"Brevity is the soul of wit."}, {"QnJldml0eSBpcyB0aGUgc291bCBvZiB3aXQu"}},
3715 {{"All that glitters is not gold."}, {"QWxsIHRoYXQgZ2xpdHRlcnMgaXMgbm90IGdvbGQu"}},
3716 };
3717 // to encode base64 and write the result to val:
3718 for(text_and_base64 c : cases)
3719 tree[c.text].set_serialized(ryml::fmt::base64(c.text));
3720 // to encode base64 and write the result to key:
3721 for(text_and_base64 c : cases)
3722 {
3723 ryml::NodeRef ch = tree.rootref().append_child();
3724 ch.set_key_serialized(ryml::fmt::base64(c.text));
3725 ch.set_serialized(c.text);
3726 }
3727 // check the result:
3728 for(text_and_base64 c : cases)
3729 {
3730 CHECK(tree[c.text].val() == c.base64);
3731 CHECK(tree[c.base64].val() == c.text);
3732 }
3733 // and this is how the YAML now looks:
3735 "Hello, World!: SGVsbG8sIFdvcmxkIQ==" "\n"
3736 "Brevity is the soul of wit.: QnJldml0eSBpcyB0aGUgc291bCBvZiB3aXQu" "\n"
3737 "All that glitters is not gold.: QWxsIHRoYXQgZ2xpdHRlcnMgaXMgbm90IGdvbGQu" "\n"
3738 // note that the keys below are base64-encoded
3739 "SGVsbG8sIFdvcmxkIQ==: Hello, World!" "\n"
3740 "QnJldml0eSBpcyB0aGUgc291bCBvZiB3aXQu: Brevity is the soul of wit." "\n"
3741 "QWxsIHRoYXQgZ2xpdHRlcnMgaXMgbm90IGdvbGQu: All that glitters is not gold." "\n"
3742 "");
3743 char buf1_[128], buf2_[128];
3744 ryml::substr buf1 = buf1_; // this is where we will write the result (using load())
3745 ryml::substr buf2 = buf2_; // this is where we will write the result (using deserialize()/deserialize_key())
3746 // to decode base64 and write the result to buf:
3747 for(const text_and_base64 c : cases)
3748 {
3749 // this decodes base64 and write the decoded result into an
3750 // existing buffer (buf1):
3751 size_t len = 0; // the decoded length
3752 tree[c.text].load(ryml::fmt::base64(buf1, &len));
3753 // The base64() tag function is used to get the
3754 // deserialization using c4::decode_base64(). This will
3755 // respect the limits of the buffer, and fail with an error if
3756 // the buffer is too small (or if the base64 encoding is
3757 // wrong). The optional second parameter is set to the decoded
3758 // size, ie, the length of the decoded result, which is also
3759 // the size required for the buffer.
3760 CHECK(len <= buf1.len);
3761 CHECK(c.text.len == len);
3762 CHECK(buf1.first(len) == c.text);
3763 // likewise for keys:
3764 tree[c.base64].load_key(ryml::fmt::base64(buf2, &len));
3765 CHECK(len <= buf2.len);
3766 CHECK(buf2.first(len) == c.text);
3767 //
3768 // interop with std::string:
3769 std::string result;
3770 tree[c.text].load(ryml::fmt::base64(result));
3771 CHECK(result == c.text);
3772 // likewise for keys:
3773 tree[c.base64].load_key(ryml::fmt::base64(result));
3774 CHECK(result == c.text);
3775 //
3776 // Manual interop with std::string: using substr.
3777 // This shows how to manually resize the destination
3778 // buffer, and is similar to the implementation for containers.
3779 result.clear(); // this is not needed. We do it just to show that the first call can fail.
3780 len = 0;
3781 // try to read into the buffer, and get back the required size
3782 // (in len)
3783 auto payload = ryml::fmt::base64(ryml::to_substr(result), &len);
3784 bool ok = tree[c.text].deserialize(payload);
3785 if(len > result.size()) // the size was not enough; resize and call again
3786 {
3787 CHECK(!ok);
3788 result.resize(len);
3789 payload = ryml::fmt::base64(ryml::to_substr(result), &len); // reassign
3790 ok = tree[c.text].deserialize(payload);
3791 }
3792 CHECK(ok);
3793 result.resize(len); // trim to the length of the decoded buffer
3794 CHECK(result == c.text);
3795 // likewise for keys
3796 }
3797 //
3798 // ryml base64() serialization uses native endianness. If you're
3799 // encoding types whose size > 1 byte, the results will vary
3800 // according to endianess. Let's use a helper here to work around
3801 // that (in practice, you should use something like htons() before
3802 // encoding):
3803 union { uint32_t u; char c[sizeof(uint32_t)]; } endianess_test = {1};
3804 const bool is_little_endian = endianess_test.c[0] == 1; // NOLINT
3805 auto endian_select = [is_little_endian](ryml::csubstr little_endian, ryml::csubstr big_endian){
3806 return is_little_endian ? little_endian : big_endian;
3807 };
3808 //
3809 // directly encode variables: integers
3810 {
3811 const uint64_t valin = UINT64_C(0xdeadbeef);
3812 ryml::NodeRef node = tree["deadbeef"];
3814 CHECK(node.val() == endian_select("776t3gAAAAA=", "AAAAAN6tvu8="));
3815 uint64_t valout = 0;
3816 size_t len = 0;
3817 node.load(ryml::fmt::base64(valout, &len));
3818 CHECK(len == sizeof(valout));
3819 CHECK(valout == UINT64_C(0xdeadbeef)); // base64 roundtrip is bit-accurate
3820 // also works without length parameter:
3821 valout = {};
3822 node.load(ryml::fmt::base64(valout));
3823 CHECK(valout == UINT64_C(0xdeadbeef)); // base64 roundtrip is bit-accurate
3824 }
3825 // directly encode variables: floating point
3826 {
3827 const double valin = 123456.7891011;
3828 ryml::NodeRef node = tree["float"];
3830 CHECK(node.val() == endian_select("nHkooAwk/kA=", "QP4kDKAoeZw="));
3831 double valout = 0;
3832 size_t len = 0;
3833 node.load(ryml::fmt::base64(valout, &len));
3834 CHECK(len == sizeof(valout));
3835 CHECK(memcmp(&valout, &valin, sizeof(valout)) == 0); // base64 roundtrip is bit-accurate // NOLINT
3836 // also works without length parameter:
3837 valout = {};
3838 node.load(ryml::fmt::base64(valout));
3839 CHECK(memcmp(&valout, &valin, sizeof(valout)) == 0); // base64 roundtrip is bit-accurate // NOLINT
3840 }
3841 // directly encode memory ranges
3842 {
3843 const uint32_t data_in[11] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xdeadbeef};
3844 uint32_t data_out[11] = {};
3845 ryml::NodeRef node = tree["int_data"];
3846 node.set_serialized(ryml::fmt::base64(data_in, C4_COUNTOF(data_in)), ryml::VAL_PLAIN);
3847 CHECK(node.val() ==
3848 endian_select("AAAAAAEAAAACAAAAAwAAAAQAAAAFAAAABgAAAAcAAAAIAAAACQAAAO++rd4=",
3849 "AAAAAAAAAAEAAAACAAAAAwAAAAQAAAAFAAAABgAAAAcAAAAIAAAACd6tvu8="));
3850 CHECK(memcmp(data_in, data_out, sizeof(data_in)) != 0); // before the roundtrip
3851 size_t len = 0;
3852 node.load(ryml::fmt::base64(data_out, C4_COUNTOF(data_in), &len));
3853 CHECK(len == sizeof(data_out));
3854 CHECK(memcmp(data_in, data_out, sizeof(data_in)) == 0); // after the roundtrip
3855 // also works without length parameter (because data_out is an
3856 // array and not a pointer):
3857 memset(data_out, 0, sizeof(data_out));
3858 node.load(ryml::fmt::base64(data_out));
3859 CHECK(memcmp(data_in, data_out, sizeof(data_in)) == 0); // after the roundtrip
3860 }
3861}
3862
3863
3864//-----------------------------------------------------------------------------
3865// Serialization info
3866
3867/** This sample shows the main user-facing calls triggering
3868 * (de)serialization. ryml provides built-ins for all fundamental
3869 * types. For samples on how to implement other types such as STL
3870 * containers or user types, see the samples below.
3871 *
3872 * Read also the [doxygen intro to using serialization](https://rapidyaml.readthedocs.io/v0.16.0/doxygen/group__doc__serialization__using.html)
3873 */
3875{
3876 ryml::csubstr yaml = "{0: 0, 10: 10, foo: foo}";
3877 ryml::Tree tree = ryml::parse_in_arena(yaml);
3878 //
3879 //
3880 // Deserialization is done with .load() and .load_key()
3881 { int val = 1; tree[0].load(&val); CHECK(val == 0); }
3882 { unsigned val = 0; tree[1].load(&val); CHECK(val == 10); }
3883 { int key = 1; tree[0].load_key(&key); CHECK(key == 0); }
3884 { unsigned key = 0; tree[1].load_key(&key); CHECK(key == 10); }
3885 // also available in the tree:
3886 { int val = 1; tree.load(tree[0].id(), &val); CHECK(val == 0); }
3887 { unsigned val = 0; tree.load(tree[1].id(), &val); CHECK(val == 10); }
3888 { int key = 1; tree.load_key(tree[0].id(), &key); CHECK(key == 0); }
3889 { unsigned key = 0; tree.load_key(tree[1].id(), &key); CHECK(key == 10); }
3890 // .load() calls the (non returning) error callback when the
3891 // serialization fails. If you want to avoid the exceptional flow,
3892 // you can use .deserialize() / .deserialize_key(), and do not forget to
3893 // check its return status (marked as `[[nodiscard]]`):
3894 { int val = 1; CHECK(tree[0].deserialize(&val)); CHECK(val == 0); }
3895 { unsigned val = 0; CHECK(tree[1].deserialize(&val)); CHECK(val == 10); }
3896 { int key = 1; CHECK(tree[0].deserialize_key(&key)); CHECK(key == 0); }
3897 { unsigned key = 0; CHECK(tree[1].deserialize_key(&key)); CHECK(key == 10); }
3898 // also available in the tree:
3899 { int val = 1; CHECK(tree.deserialize(tree[0].id(), &val)); CHECK(val == 0); }
3900 { unsigned val = 0; CHECK(tree.deserialize(tree[1].id(), &val)); CHECK(val == 10); }
3901 { int key = 1; CHECK(tree.deserialize_key(tree[0].id(), &key)); CHECK(key == 0); }
3902 { unsigned key = 0; CHECK(tree.deserialize_key(tree[1].id(), &key)); CHECK(key == 10); }
3903 //
3904 //
3905 // Serialization is done with .save() and .save_key(). It is
3906 // carried out by converting the value to string in the tree's
3907 // arena (see sample_tree_arena()).
3908 { int val = 10; tree[0].save(val); CHECK(tree[0].val() == "10"); }
3909 { unsigned val = 11; tree[1].save(val); CHECK(tree[1].val() == "11"); }
3910 { int key = 12; tree[0].save_key(key); CHECK(tree[0].key() == "12"); }
3911 { unsigned key = 13; tree[1].save_key(key); CHECK(tree[1].key() == "13"); }
3912 // also available in the tree:
3913 { int val = 20; tree.save(tree[0].id(), val); CHECK(tree[0].val() == "20"); }
3914 { unsigned val = 21; tree.save(tree[1].id(), val); CHECK(tree[1].val() == "21"); }
3915 { int key = 22; tree.save_key(tree[0].id(), key); CHECK(tree[0].key() == "22"); }
3916 { unsigned key = 23; tree.save_key(tree[1].id(), key); CHECK(tree[1].key() == "23"); }
3917 //
3918 //
3919 // Like .load(), .save() checks the node for write-ability and
3920 // triggers an error if it failed. Likewise, to avoid the
3921 // exceptional path, you can use .set_serialized() and
3922 // .set_key_serialized():
3923 { int val = 14; tree[0].set_serialized(val); CHECK(tree[0].val() == "14"); }
3924 { unsigned val = 15; tree[1].set_serialized(val); CHECK(tree[1].val() == "15"); }
3925 { int key = 16; tree[0].set_key_serialized(key); CHECK(tree[0].key() == "16"); }
3926 { unsigned key = 17; tree[1].set_key_serialized(key); CHECK(tree[1].key() == "17"); }
3927 /// And likewise, you can use these from the tree as well:
3928 /// @ref c4::yml::Tree::set_serialized() / @ref c4::yml::Tree::set_key_serialized().
3929 { int val = 18; tree.set_serialized(tree[0].id(), val); CHECK(tree[0].val() == "18"); }
3930 { unsigned val = 19; tree.set_serialized(tree[1].id(), val); CHECK(tree[1].val() == "19"); }
3931 { int key = 20; tree.set_key_serialized(tree[0].id(), key); CHECK(tree[0].key() == "20"); }
3932 { unsigned key = 21; tree.set_key_serialized(tree[1].id(), key); CHECK(tree[1].key() == "21"); }
3933 //
3934 //
3935 // You can also (de)serialize tags and even anchors, if your
3936 // application requires it. For serialization the trick is to use
3937 // .to_arena():
3938 tree[0].set_val_tag(tree[0].to_arena(42)); CHECK(tree[0].val_tag() == "42");
3939 tree[1].set_key_tag(tree[1].to_arena(43)); CHECK(tree[1].key_tag() == "43");
3940 tree[0].set_val_anchor(tree[0].to_arena(44)); CHECK(tree[0].val_anchor() == "44");
3941 tree[1].set_key_anchor(tree[1].to_arena(45)); CHECK(tree[1].key_anchor() == "45");
3942 /// For deserialization, use from_chars().
3943 { int val = 0; CHECK(from_chars(tree[0].val_tag(), &val)); CHECK(val == 42); }
3944 { unsigned key = 0; CHECK(from_chars(tree[1].key_tag(), &key)); CHECK(key == 43); }
3945 { int val = 0; CHECK(from_chars(tree[0].val_anchor(), &val)); CHECK(val == 44); }
3946 { unsigned key = 0; CHECK(from_chars(tree[1].key_anchor(), &key)); CHECK(key == 45); }
3947}
3948
3949
3950//-----------------------------------------------------------------------------
3951// ryml uses Argument Dependent Lookup to dispatch the serialization
3952// to each type. This enables the user to implement serialization for
3953// custom types (and also enables rapidyaml to implement serialization
3954// of fundamental types).
3955//
3956// user scalar types: where nothing in the tree needs to be set.
3957// implemented in ryml through to_chars() + from_chars()
3958
3959/** @addtogroup doc_quickstart_helpers
3960 * @{ */
3961
3962/** @defgroup doc_sample_scalar_types Serialize/deserialize scalar types
3963 * @{ */
3964
3965// IMPORTANT: read the doxygen documentation for scalar serialization at:
3966// https://rapidyaml.readthedocs.io/v0.16.0/doxygen/group__doc__serialization__user__types.html
3967
3968template<class T> struct vec2 { T x, y; }; ///< example scalar type, serialized and deserialized
3969template<class T> struct vec3 { T x, y, z; }; ///< example scalar type, serialized and deserialized
3970template<class T> struct vec4 { T x, y, z, w; }; ///< example scalar type, serialized and deserialized
3971
3972template<class T> struct parse_only_vec2 { T x, y; }; ///< example scalar type, deserialized only
3973template<class T> struct parse_only_vec3 { T x, y, z; }; ///< example scalar type, deserialized only
3974template<class T> struct parse_only_vec4 { T x, y, z, w; }; ///< example scalar type, deserialized only
3975
3976template<class T> struct emit_only_vec2 { T x, y; }; ///< example scalar type, serialized only
3977template<class T> struct emit_only_vec3 { T x, y, z; }; ///< example scalar type, serialized only
3978template<class T> struct emit_only_vec4 { T x, y, z, w; }; ///< example scalar type, serialized only
3979
3980
3981// to serialize scalars, you need to define to_chars():
3982
3983template<class T> size_t to_chars(ryml::substr buf, vec2<T> v) { return ryml::format(buf, "({},{})", v.x, v.y); }
3984template<class T> size_t to_chars(ryml::substr buf, vec3<T> v) { return ryml::format(buf, "({},{},{})", v.x, v.y, v.z); }
3985template<class T> size_t to_chars(ryml::substr buf, vec4<T> v) { return ryml::format(buf, "({},{},{},{})", v.x, v.y, v.z, v.w); }
3986
3987template<class T> size_t to_chars(ryml::substr buf, emit_only_vec2<T> v) { return ryml::format(buf, "({},{})", v.x, v.y); }
3988template<class T> size_t to_chars(ryml::substr buf, emit_only_vec3<T> v) { return ryml::format(buf, "({},{},{})", v.x, v.y, v.z); }
3989template<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); }
3990
3991
3992// to deserialize scalars, you need to define from_chars():
3993
3994template<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; }
3995template<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; }
3996template<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; }
3997
3998template<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; }
3999template<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; }
4000template<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; }
4001
4002
4003// IMPORTANT: read the doxygen documentation for scalar serialization at:
4004// https://rapidyaml.readthedocs.io/v0.16.0/doxygen/group__doc__serialization__user__types.html
4005
4006/** @} */ // doc_sample_scalar_types
4007/** @} */ // doc_quickstart_helpers
4008
4009
4010/** to add scalar types (ie leaf types converting to/from string),
4011 * define the functions above for those types.
4012 *
4013 * @note read the doxygen documentation for scalar types at:
4014 * https://rapidyaml.readthedocs.io/v0.16.0/doxygen/group__doc__serialization__user__types.html
4015 *
4016 * See @ref
4017 * doc_sample_scalar_types. */
4019{
4020 ryml::Tree t;
4021 ryml::NodeRef root = t;
4022 root.set_map();
4023
4024 vec2<int> v2in{10, 11};
4025 vec2<int> v2out{1, 2};
4026 root["v2"].save(v2in); // serializes to the tree's arena, and then sets the val
4027 root["v2"].load(&v2out);
4028 CHECK(v2in.x == v2out.x);
4029 CHECK(v2in.y == v2out.y);
4030 vec3<int> v3in{100, 101, 102};
4031 vec3<int> v3out{1, 2, 3};
4032 root["v3"].save(v3in); // serializes to the tree's arena, and then sets the val
4033 root["v3"].load(&v3out);
4034 CHECK(v3in.x == v3out.x);
4035 CHECK(v3in.y == v3out.y);
4036 CHECK(v3in.z == v3out.z);
4037 vec4<int> v4in{1000, 1001, 1002, 1003};
4038 vec4<int> v4out{1, 2, 3, 4};
4039 root["v4"].save(v4in); // serializes to the tree's arena, and then sets the val
4040 root["v4"].load(&v4out);
4041 CHECK(v4in.x == v4out.x);
4042 CHECK(v4in.y == v4out.y);
4043 CHECK(v4in.z == v4out.z);
4044 CHECK(v4in.w == v4out.w);
4046 "v2: (10,11)" "\n"
4047 "v3: (100,101,102)" "\n"
4048 "v4: (1000,1001,1002,1003)" "\n"
4049 "");
4050
4051 // note that only the used functions are needed:
4052 // - if a type is only parsed, then only from_chars() is needed
4053 // - if a type is only emitted, then only to_chars() is needed
4054 emit_only_vec2<int> eov2in{20, 21}; // only has to_chars()
4055 parse_only_vec2<int> pov2out{1, 2}; // only has from_chars()
4056 root["v2"].save(eov2in); // serializes to the tree's arena, and then sets the keyval
4057 root["v2"].load(&pov2out);
4058 CHECK(eov2in.x == pov2out.x);
4059 CHECK(eov2in.y == pov2out.y);
4060 emit_only_vec3<int> eov3in{30, 31, 32}; // only has to_chars()
4061 parse_only_vec3<int> pov3out{1, 2, 3}; // only has from_chars()
4062 root["v3"].save(eov3in); // serializes to the tree's arena, and then sets the keyval
4063 root["v3"].load(&pov3out);
4064 CHECK(eov3in.x == pov3out.x);
4065 CHECK(eov3in.y == pov3out.y);
4066 CHECK(eov3in.z == pov3out.z);
4067 emit_only_vec4<int> eov4in{40, 41, 42, 43}; // only has to_chars()
4068 parse_only_vec4<int> pov4out{1, 2, 3, 4}; // only has from_chars()
4069 root["v4"].save(eov4in); // serializes to the tree's arena, and then sets the keyval
4070 root["v4"].load(&pov4out);
4071 CHECK(eov4in.x == pov4out.x);
4072 CHECK(eov4in.y == pov4out.y);
4073 CHECK(eov4in.z == pov4out.z);
4075 "v2: (20,21)" "\n"
4076 "v3: (30,31,32)" "\n"
4077 "v4: (40,41,42,43)" "\n"
4078 "");
4079}
4080
4081
4082//-----------------------------------------------------------------------------
4083// ryml uses C++'s Argument Dependent Lookup to dispatch the serialization
4084// to each type. This enables the user to implement serialization for
4085// custom types (and also enables rapidyaml to implement serialization
4086// of fundamental types).
4087
4088/** @addtogroup doc_quickstart_helpers
4089 * @{ */
4090
4091/** @defgroup doc_sample_container_types_brief Serialize container types (brief)
4092 *
4093 * Serialization definitions used in @ref sample_user_container_types_brief()
4094 *
4095 * @{ */
4096
4097// let's start with a brief, minimal example, and expand below.
4098
4099// first define an example nested user type:
4100struct Inner { int foo, bar; }; /// serializes as a map
4101struct Outer { Inner inner[2]; }; /// serializes as a seq
4102
4103// now define the write functions:
4104void write(ryml::NodeRef &n, Inner const& inner)
4105{
4107 n["foo"].set_serialized(inner.foo);
4108 n["bar"].set_serialized(inner.bar);
4109}
4110void write(ryml::NodeRef &n, Outer const& outer)
4111{
4113 n[0].set_serialized(outer.inner[0]);
4114 n[1].set_serialized(outer.inner[1]);
4115}
4116
4117// now define the read functions:
4119{
4120 ryml::ReadResult r(n.is_map(), n.id());
4121 if(r) r = n.deserialize_child("foo", &inner->foo); // O(N)
4122 if(r) r = n.deserialize_child("bar", &inner->bar);
4123 return r;
4124}
4126{
4127 ryml::ReadResult r(n.is_seq(), n.id());
4128 // size is fixed, so we do this with explicit indices.
4129 if(r) r = n.deserialize_child(0, &val->inner[0]); // O(N)
4130 if(r) r = n.deserialize_child(1, &val->inner[1]);
4131 return r;
4132}
4133
4134/** @} */
4135
4136/** @} */
4137
4138/** shows a minimal example of user container types, defined in @ref doc_sample_container_types_brief
4139 *
4140 * @note read the doxygen documentation for containers/general types at:
4141 * https://rapidyaml.readthedocs.io/v0.16.0/doxygen/group__doc__serialization__user__types.html
4142 */
4144{
4145 ryml::Tree tree;
4146 ryml::NodeRef root = tree;
4147 Outer outer{{{0,1}, {2,3}}};
4148 Outer roundtrip;
4149 // let's do a serialization roundtrip.
4150 // first serialize:
4151 root.save(outer);
4152 // see how the YAML looks:
4154 "- {foo: 0,bar: 1}\n"
4155 "- {foo: 2,bar: 3}\n");
4156 // now complete the roundtrip by deserializing:
4157 root.load(&roundtrip);
4158 // finally, verify that the result is equal:
4159 CHECK(0 == memcmp(&outer, &roundtrip, sizeof(Outer)));
4160}
4161
4162
4163//-----------------------------------------------------------------------------
4164// let's now elaborate further
4165
4166/** @addtogroup doc_quickstart_helpers
4167 * @{ */
4168
4169/** @defgroup doc_sample_container_types Serialize container types
4170 *
4171 * Serialization definitions used in @ref sample_user_container_types()
4172 *
4173 * @{ */
4174
4175/** example user container type: seq-like */
4176template<class T>
4178{
4179 std::vector<T> seq_member;
4180 void check_eq(my_seq_type const& that) const
4181 {
4182 CHECK(seq_member.size() == that.seq_member.size());
4183 if(seq_member.size() == that.seq_member.size())
4184 for(size_t i = 0; i < seq_member.size(); ++i)
4185 CHECK(seq_member[i] == that.seq_member[i]);
4186 }
4187};
4188/** example user container type: map-like */
4189template<class K, class V>
4191{
4192 std::map<K, V> map_member;
4193 void check_eq(my_map_type const& that) const
4194 {
4195 CHECK(map_member.size() == that.map_member.size());
4196 if(map_member.size() == that.map_member.size())
4197 {
4198 for(auto const& itthat : that.map_member)
4199 {
4200 auto it = map_member.find(itthat.first);
4201 CHECK(it != map_member.end());
4202 if(it != map_member.end())
4203 CHECK(it->second == itthat.second);
4204 }
4205 }
4206 }
4207};
4208/** example user container type with nested user types.
4209 * notice all the members have user-defined serialization methods. */
4211{
4212 // these are serialized as scalar (leaf) nodes:
4216 // these are serialized as container nodes:
4219 void check_eq(my_type const& that) const
4220 {
4221 CHECK(v2.x == that.v2.x);
4222 CHECK(v2.y == that.v2.y);
4223 CHECK(v3.x == that.v3.x);
4224 CHECK(v3.y == that.v3.y);
4225 CHECK(v3.z == that.v3.z);
4226 CHECK(v4.x == that.v4.x);
4227 CHECK(v4.y == that.v4.y);
4228 CHECK(v4.z == that.v4.z);
4229 CHECK(v4.w == that.v4.w);
4230 seq.check_eq(that.seq);
4231 map.check_eq(that.map);
4232 }
4233};
4234
4235
4236// IMPORTANT: read the doxygen documentation for deserialization of user types:
4237// https://rapidyaml.readthedocs.io/v0.16.0/doxygen/group__doc__serialization__user__types.html
4238
4239
4240// Let's first show examples of serialization (writing) functions:
4241// writing is easier because it does not have to check the data.
4242
4243// here's an example of serializing a seq type
4244template<class T>
4246{
4247 tree->set_seq(id);
4248 for(T const& v : seq.seq_member)
4249 {
4250 // inside write(), prefer using .set_serialized() instead of .save()
4251 tree->set_serialized(tree->append_child(id), v);
4252 }
4253}
4254// special optimization: if all you have is strings, AND you are sure
4255// they outlive the tree, you can avoid the copy to the tree's
4256// arena. But beware the lifetime issue!
4258{
4259 tree->set_seq(id);
4260 for(std::string const& v : seq.seq_member)
4261 {
4262 // now the tree is pointing at seq's strings. using .set_val()
4263 // does not serialize, and this avoids the string copy to the
4264 // tree's arena
4265 tree->set_val(tree->append_child(id), ryml::to_csubstr(v));
4266 }
4267}
4268
4269// example of map serialization:
4270template<class K, class V>
4272{
4273 tree->set_map(id);
4274 for(auto const& v : map.map_member)
4275 {
4276 // inside write(), prefer using .set_serialized() instead of .save()
4277 ryml::id_type child_id = tree->append_child(id);
4278 tree->set_key_serialized(child_id, v.first); // we're serializing the key!
4279 tree->set_serialized(child_id, v.second);
4280 }
4281}
4282// another example of map serialization:
4283void write(ryml::Tree *tree, ryml::id_type id, my_type const& val)
4284{
4285 tree->set_map(id);
4286 // inside write(), prefer using .set_serialized() instead of .save()
4287 //
4288 ryml::id_type ch;
4289 // these are leaf nodes:
4290 ch = tree->append_child(id); tree->set_key(ch, "v2"); tree->set_serialized(ch, val.v2);
4291 ch = tree->append_child(id); tree->set_key(ch, "v3"); tree->set_serialized(ch, val.v3);
4292 ch = tree->append_child(id); tree->set_key(ch, "v4"); tree->set_serialized(ch, val.v4);
4293 // these are container nodes (note how the call is equal):
4294 ch = tree->append_child(id); tree->set_key(ch, "seq"); tree->set_serialized(ch, val.seq);
4295 ch = tree->append_child(id); tree->set_key(ch, "map"); tree->set_serialized(ch, val.map);
4296 // Note above that we're NOT serializing the keys. That works and
4297 // is correct here because the keys themselves are fixed, and are
4298 // static strings located in the executable. But if the keys came
4299 // from the data, they too would have to be serialized with
4300 // .set_key_serialized().
4301}
4302
4303
4304// Now let's implement the read() functions. Here we have more work to
4305// do because in general we need to check the sanity of the data
4306// coming from YAML.
4307//
4308// IMPORTANT: read the doxygen documentation for deserialization of user types:
4309// https://rapidyaml.readthedocs.io/v0.16.0/doxygen/group__doc__serialization__user__types.html
4310
4311
4312template<class T>
4314{
4315 if(!tree->is_seq(id)) return ryml::ReadResult(id); // id must be a seq
4316 seq->seq_member.clear(); // we'll overwrite the vector
4317 for(ryml::id_type child = tree->first_child(id);
4318 child != ryml::NONE;
4319 child = tree->next_sibling(child))
4320 {
4321 // create a new entry
4322 seq->seq_member.emplace_back();
4323 // inside read() you SHOULD NOT use .load() because of its
4324 // exceptional flow. Instead, you should use .deserialize() to
4325 // play nice with .deserialize() callers calling this function.
4326 // Read more at the doxygen page linked above.
4327 ryml::ReadResult r = tree->deserialize(child, &seq->seq_member.back());
4328 if(!r) return r; // return the inner-most result
4329 }
4330 return ryml::ReadResult(); // all good
4331}
4332
4333template<class K, class V>
4335{
4336 if(!tree->is_map(id)) return ryml::ReadResult(id); // id must be a seq
4337 for(ryml::id_type child = tree->first_child(id);
4338 child != ryml::NONE;
4339 child = tree->next_sibling(child))
4340 {
4341 K k{};
4342 // again, we're using .deserialize() instead of .load()
4343 // because (1) we should gracefully return a ReadResult and
4344 // anyway (2) we're certain the node is readable (because
4345 // we're inside the loop).
4346 ryml::ReadResult r = tree->deserialize_key(child, &k);
4347 if(r) r = tree->deserialize(child, &map->map_member[std::move(k)]);
4348 if(!r) return r; // return the inner-most result
4349 }
4350 return ryml::ReadResult(); // all good.
4351}
4352
4353
4354//
4355// Given that ryml provides both a tree and a node API, you can
4356// implement a tree read() (as above), or a node read(). The latter
4357// will only be called if you trigger deserialization from a
4358// node. That's ok here, because that's what we're doing below.
4359//
4360// Regardless, it needs to gracefully handle bad YAML data, so we must
4361// check every step. We use .deserialize_child() which will look for a
4362// child by key, and deserialize that child, returning the deserialize
4363// result. If no such child exists, it returns a read result reporting
4364// the node (not the child). .deserialize_child() also works with
4365// indices, simplifying the implementation of fixed-size seqs.
4366//
4367// Note also how each step is predicated on the latest result. This
4368// will ensure that the first failing step is reported.
4370{
4371 ryml::ReadResult r(n.is_map(), n.id()); // node must be a map
4372 if(r) r = n.deserialize_child("v2", &val->v2);
4373 if(r) r = n.deserialize_child("v3", &val->v3);
4374 if(r) r = n.deserialize_child("v4", &val->v4);
4375 if(r) r = n.deserialize_child("seq", &val->seq);
4376 if(r) r = n.deserialize_child("map", &val->map);
4377 // hint: you can also add a default argument for when no such child exists
4378 // hint: you can also use indices instead of keys
4379 return r;
4380}
4381// But if you're going to ever deserialize from a tree+id, then, you
4382// must implement a tree+id read(). Also, if you only implement the
4383// tree+id read(), it will get picked both from tree calls or from
4384// node calls when no node read() exists. That is the reason why you
4385// should prefer to implement the tree version. As an example, see how
4386// similar the tree version is:
4388{
4389 ryml::ReadResult r(tree->is_map(id), id); // node must be a map
4390 if(r) r = tree->deserialize_child(id, "v2", &val->v2);
4391 if(r) r = tree->deserialize_child(id, "v3", &val->v3);
4392 if(r) r = tree->deserialize_child(id, "v4", &val->v4);
4393 if(r) r = tree->deserialize_child(id, "seq", &val->seq);
4394 if(r) r = tree->deserialize_child(id, "map", &val->map);
4395 // hint: you can also add a default argument for when no such child exists
4396 // hint: you can also use indices instead of keys
4397 return r;
4398}
4399
4400/** @} */ // doc_sample_container_types
4401
4402/** @} */ // quickstart_helpers
4403
4404
4405/** shows how to serialize/deserialize container types (defined in @ref doc_sample_container_types).
4406 *
4407 * @note read the doxygen documentation for containers/general types at:
4408 * https://rapidyaml.readthedocs.io/v0.16.0/doxygen/group__doc__serialization__user__types.html
4409 *
4410 * @see doc_sample_container_types
4411 * @see sample_std_types
4412 * @see sample_deserialize_error
4413 * */
4415{
4416 // let's do a YAML roundtrip:
4417 ryml::Tree tree;
4418 ryml::NodeRef root_node = tree;
4419 ryml::id_type root_id = tree.root_id();
4420
4421 // here we will be doing a serialization roundtrip with a
4422 // user-defined container type.
4423 //
4424 // the read() and write() functions for this type are defined
4425 // above.
4426 const my_type orig{
4427 {20, 21},
4428 {30, 31, 32},
4429 {40, 41, 42, 43},
4430 {{101, 102, 103, 104, 105, 106, 107}},
4431 {{{1001, 2001}, {1002, 2002}, {1003, 2003}}},
4432 };
4433
4434 // serialize to the tree:
4435 root_node.save(orig);
4436 // check the YAML:
4438 "v2: (20,21)" "\n"
4439 "v3: (30,31,32)" "\n"
4440 "v4: (40,41,42,43)" "\n"
4441 "seq:" "\n"
4442 " - 101" "\n"
4443 " - 102" "\n"
4444 " - 103" "\n"
4445 " - 104" "\n"
4446 " - 105" "\n"
4447 " - 106" "\n"
4448 " - 107" "\n"
4449 "map:" "\n"
4450 " 1001: 2001" "\n"
4451 " 1002: 2002" "\n"
4452 " 1003: 2003" "\n"
4453 "");
4454 // and now let's deserialize to this variable.
4455 // first from a node:
4456 {
4457 my_type roundtrip;
4458 root_node.load(&roundtrip); // picks the node read(), because we wrote one.
4459 // if we didn't, it would then pick the tree read()
4460 roundtrip.check_eq(orig); // finally, let's compare.
4461 }
4462 // let's also deserialize from the tree:
4463 {
4464 my_type roundtrip;
4465 tree.load(root_id, &roundtrip); // will pick the tree read()
4466 roundtrip.check_eq(orig); // finally, let's compare.
4467 }
4468
4469 // We created above a my_seq<std::string> specialization showing
4470 // that we can use write() without serializing the data to the
4471 // tree's arena. Let's show it working here:
4472 const my_seq_type<std::string> strseq{{
4473 "doe",
4474 "a deer, a female deer",
4475 "ray",
4476 "a drop of golden sun"
4477 }};
4478 // serialize it to a nested node in the tree:
4479 tree["not in arena"].save(strseq);
4480 // check the YAML:
4481 CHECK(ryml::emitrs_yaml<std::string>(tree["not in arena"]) == ""
4482 // note the new elements:
4483 "not in arena:" "\n"
4484 " - doe" "\n"
4485 " - a deer, a female deer" "\n"
4486 " - ray" "\n"
4487 " - a drop of golden sun" "\n"
4488 "");
4489 // show how the strings are NOT in the tree's arena
4490 size_t pos = 0;
4491 CHECK(strseq.seq_member.size() == tree["not in arena"].num_children());
4492 for(ryml::ConstNodeRef child : tree["not in arena"].children())
4493 {
4494 ryml::csubstr str_orig = ryml::to_csubstr(strseq.seq_member[pos++]);
4495 CHECK(child.val() == str_orig); // same string
4496 CHECK(!child.val().is_sub(tree.arena())); // not in the tree's arena
4497 CHECK(child.val().is_sub(str_orig)); // ... but the original memory
4498 }
4499}
4500
4501
4502//-----------------------------------------------------------------------------
4503
4504/** shows what happens on deserialization errors
4505 *
4506 * @note read the doxygen documentation for containers/general types at:
4507 * https://rapidyaml.readthedocs.io/v0.16.0/doxygen/group__doc__serialization__user__types.html
4508 *
4509 * @see sample_user_container_types()
4510 * @see sample_error_visit_location()
4511 * @see sample_location_tracking()
4512 */
4514{
4515 // in this example we will be checking errors, so set up a
4516 // temporary error handler to catch them:
4517 ScopedErrorHandlerExample errh; // calls ryml::set_callbacks()
4518 // let's parse this YAML containing an invalid value,
4519 // with location tracking:
4520 ryml::EventHandlerTree tree_handler;
4521 ryml::Parser parser(&tree_handler, ryml::ParserOptions{}.locations(true));
4522 const ryml::Tree tree = parse_in_arena(&parser,
4523 "v2: (20,21)" "\n"
4524 "v3: (30,31,32)" "\n"
4525 "v4: (40,41,42,43)" "\n"
4526 "seq:" "\n"
4527 " - 101" "\n"
4528 " - 102" "\n"
4529 " - 103" "\n"
4530 " - 104" "\n"
4531 " - 105" "\n"
4532 " - 106" "\n"
4533 " - 107" "\n"
4534 "map:" "\n"
4535 " 1001: 2001" "\n"
4536 " 1002: not an int" "\n" // valid YAML, will cause deserialization error
4537 " 1003: 2003" "\n"
4538 "");
4539 ryml::ConstNodeRef root = tree;
4540 // and now let's deserialize to this variable
4541 my_type var;
4542 // .deserialize() reports the error but does not trigger an error
4543 // call:
4544 ryml::ReadResult result = root.deserialize(&var);
4545 CHECK(!result);
4546 CHECK(result.node == tree["map"]["1002"].id()); // this is where the error occurred
4547 CHECK(tree.location(parser, result.node).line == 13);
4548 CHECK(tree.location(parser, result.node).col == 2); // this node starts a column 2
4549 CHECK(parser.val_location(tree.val(result.node).str).col == 8); // its value starts at column 8
4550 // and .load() will trigger a visit error, reporting the node as
4551 // well. see sample_error_visit_location() for examples on how to
4552 // track the location of a visit error
4553 errh.check_error_occurs([&]{ root.load(&var); });
4554}
4555
4556
4557//-----------------------------------------------------------------------------
4558
4559/** demonstrates usage with the std implementations provided by ryml
4560 in the ryml_std.hpp header
4561 @see @ref doc_sample_container_types
4562 @see also the STL section in @ref doc_serialization */
4564{
4565 // we're using C-strings because doxygen breaks down on raw strings
4566 std::string yml_std_string = ""
4567 "- v2: (20,21)" "\n"
4568 " v3: (30,31,32)" "\n"
4569 " v4: (40,41,42,43)" "\n"
4570 " seq:" "\n"
4571 " - 101" "\n"
4572 " - 102" "\n"
4573 " - 103" "\n"
4574 " - 104" "\n"
4575 " - 105" "\n"
4576 " - 106" "\n"
4577 " - 107" "\n"
4578 " map:" "\n"
4579 " 1001: 2001" "\n"
4580 " 1002: 2002" "\n"
4581 " 1003: 2003" "\n"
4582 "- v2: (120,121)" "\n"
4583 " v3: (130,131,132)" "\n"
4584 " v4: (140,141,142,143)" "\n"
4585 " seq:" "\n"
4586 " - 1101" "\n"
4587 " - 1102" "\n"
4588 " - 1103" "\n"
4589 " - 1104" "\n"
4590 " - 1105" "\n"
4591 " - 1106" "\n"
4592 " - 1107" "\n"
4593 " map:" "\n"
4594 " 11001: 12001" "\n"
4595 " 11002: 12002" "\n"
4596 " 11003: 12003" "\n"
4597 "- v2: (220,221)" "\n"
4598 " v3: (230,231,232)" "\n"
4599 " v4: (240,241,242,243)" "\n"
4600 " seq:" "\n"
4601 " - 2101" "\n"
4602 " - 2102" "\n"
4603 " - 2103" "\n"
4604 " - 2104" "\n"
4605 " - 2105" "\n"
4606 " - 2106" "\n"
4607 " - 2107" "\n"
4608 " map:" "\n"
4609 " 21001: 22001" "\n"
4610 " 21002: 22002" "\n"
4611 " 21003: 22003" "\n"
4612 "";
4613 // parse in-place using the std::string above
4614 ryml::Tree tree = ryml::parse_in_place(ryml::to_substr(yml_std_string));
4615 // my_type is a container-of-containers type. see above its
4616 // definition implementation for ryml.
4617 std::vector<my_type> vec;
4618 tree.rootref().load(&vec);
4619 CHECK(vec.size() == 3);
4620 ryml::Tree tree_out;
4621 tree_out.rootref().save(vec);
4622 CHECK(ryml::emitrs_yaml<std::string>(tree_out) == yml_std_string);
4623}
4624
4625
4626//-----------------------------------------------------------------------------
4627
4628/** control precision of serialized floats */
4630{
4631 std::vector<double> reference{1.23234412342131234, 2.12323123143434237, 3.67847983572591234};
4632 // A safe precision for comparing doubles. May vary depending on
4633 // compiler flags. Double goes to about 15 digits, so 14 should be
4634 // safe enough for this test to succeed.
4635 const double precision_safe = 1.e-14;
4636 const size_t num_digits_safe = 14;
4637 const size_t num_digits_original = 17;
4638 auto get_num_digits = [](ryml::csubstr number){ return number.len - 2u; };
4639 //
4640 // no significant precision is lost when reading
4641 // floating point numbers:
4642 {
4643 ryml::Tree tree = ryml::parse_in_arena("[1.23234412342131234, 2.12323123143434237, 3.67847983572591234]");
4644 std::vector<double> output;
4645 tree.rootref().load(&output);
4646 CHECK(output.size() == reference.size());
4647 for(size_t i = 0; i < reference.size(); ++i)
4648 {
4649 CHECK(get_num_digits(tree[(ryml::id_type)i].val()) == num_digits_original);
4650 CHECK(fabs(output[i] - reference[i]) < precision_safe);
4651 }
4652 }
4653 //
4654 // However, depending on the compilation settings, there may be a
4655 // significant precision loss when serializing with the default
4656 // approach, .save(double):
4657 {
4658 ryml::Tree serialized;
4659 serialized.rootref().save(reference);
4660 // Without std::to_chars() there is a loss of precision:
4661 #if (!C4CORE_HAVE_STD_TOCHARS) // check if std::to_chars() is available.
4662 CHECK((ryml::emitrs_yaml<std::string>(serialized) == ""
4663 "- 1.23234" "\n"
4664 "- 2.12323" "\n"
4665 "- 3.67848" "\n"
4666 "") || (bool)"this is indicative; the exact results will vary from platform to platform.");
4667 C4_UNUSED(num_digits_safe);
4668 // ... but when using C++17 and above, the results are eminently equal:
4669 #else
4670 CHECK((ryml::emitrs_yaml<std::string>(serialized) == ""
4671 "- 1.2323441234213124" "\n"
4672 "- 2.1232312314343424" "\n"
4673 "- 3.6784798357259123" "\n"
4674 "") || (bool)"this is indicative; the exact results will vary from platform to platform.");
4675 size_t pos = 0;
4676 for(ryml::ConstNodeRef child : serialized.rootref().children())
4677 {
4678 CHECK(get_num_digits(child.val()) >= num_digits_safe);
4679 double out = {};
4680 child.load(&out);
4681 CHECK(fabs(out - reference[pos++]) < precision_safe);
4682 }
4683 #endif
4684 }
4685 //
4686 // The difference is explained by the availability of
4687 // fastfloat::from_chars(), std::from_chars() and std::to_chars().
4688 //
4689 // ryml prefers the fastfloat::from_chars() version. Unfortunately
4690 // fastfloat does not have to_chars() (see
4691 // https://github.com/fastfloat/fast_float/issues/23).
4692 //
4693 // When C++17 is used, ryml uses std::to_chars(), which produces
4694 // good defaults.
4695 //
4696 // However, with earlier standards, or in some library
4697 // implementations, there's only snprintf() available. Every other
4698 // std library function will either disrespect the string limits,
4699 // or more precisely, accept no string size limits. So the
4700 // implementation of c4core (which ryml uses) falls back to
4701 // snprintf("%g"), and that picks by default a (low) number of
4702 // digits.
4703 //
4704 // But not all is lost for C++11/C++14 users!
4705 //
4706 // To force a particular precision when serializing, you can use
4707 // c4::fmt::real() (brought into the ryml:: namespace). Or you can
4708 // serialize the number yourself! The small downside is that you
4709 // have to build the container.
4710 //
4711 // First a function to check the result:
4712 auto check_precision = [&](ryml::Tree const& serialized){
4713 std::cout << serialized;
4714 // now it works!
4715 CHECK((ryml::emitrs_yaml<std::string>(serialized) == ""
4716 "- 1.23234412342131239" "\n"
4717 "- 2.12323123143434245" "\n"
4718 "- 3.67847983572591231" "\n"
4719 "") || (bool)"this is indicative; the exact results will vary from platform to platform.");
4720 size_t pos = 0;
4721 for(ryml::ConstNodeRef child : serialized.rootref().children())
4722 {
4723 CHECK(get_num_digits(child.val()) == num_digits_original);
4724 double out = {};
4725 child.load(&out);
4726 CHECK(fabs(out - reference[pos++]) < precision_safe);
4727 }
4728 };
4729 //
4730 // Serialization example using fmt::real()
4731 {
4732 ryml::Tree serialized;
4733 ryml::NodeRef root = serialized.rootref();
4734 root.set_seq();
4735 for(const double v : reference)
4736 root.append_child().save(ryml::fmt::real(v, num_digits_original, ryml::FTOA_FLOAT));
4737 check_precision(serialized); // OK - now within bounds!
4738 }
4739 //
4740 // Serialization example using snprintf
4741 {
4742 ryml::Tree serialized;
4743 ryml::NodeRef root = serialized.rootref();
4744 root.set_seq();
4745 char tmp[64];
4746 for(const double v : reference)
4747 {
4748 // reuse a buffer to serialize.
4749 // add 1 to the significant digits because the %g
4750 // specifier counts the integral digits.
4751 (void)snprintf(tmp, sizeof(tmp), "%.18g", v);
4752 // copy the serialized string to the tree (.save()
4753 // copies to the arena, .set_val() just assigns the string
4754 // pointer and would be wrong in this case):
4755 root.append_child().save(ryml::to_csubstr((const char*)tmp));
4756 }
4757 check_precision(serialized); // OK - now within bounds!
4758 }
4759}
4760
4761
4762//-----------------------------------------------------------------------------
4763
4764/** demonstrates how to emit to a linear container of char */
4766{
4767 ryml::csubstr ymla =
4768 "- 1\n"
4769 "- 2\n"
4770 "";
4771 ryml::csubstr ymlb =
4772 "- a" "\n"
4773 "- b" "\n"
4774 "- x0: 1" "\n"
4775 " x1: 2" "\n"
4776 "- champagne: Dom Perignon" "\n"
4777 " coffee: Arabica" "\n"
4778 " more:" "\n"
4779 " vinho verde: Soalheiro" "\n"
4780 " vinho tinto: Redoma 2017" "\n"
4781 " beer:" "\n"
4782 " - Rochefort 10" "\n"
4783 " - Busch" "\n"
4784 " - Leffe Rituel" "\n"
4785 "- foo" "\n"
4786 "- bar" "\n"
4787 "- baz" "\n"
4788 "- bat" "\n"
4789 "";
4790 const ryml::Tree treea = ryml::parse_in_arena(ymla);
4791 const ryml::Tree treeb = ryml::parse_in_arena(ymlb);
4792
4793 // it is possible to emit to any linear container of char.
4794 //
4795 // eg, std::vector<char>
4796 {
4797 // do a blank call on an empty buffer to find the required size.
4798 // no overflow will occur, and returns a substr with the size
4799 // required to output
4800 ryml::csubstr output = ryml::emit_yaml(treea, treea.root_id(), ryml::substr{}, /*error_on_excess*/false);
4801 CHECK(output.str == nullptr);
4802 CHECK(output.len > 0);
4803 size_t num_needed_chars = output.len;
4804 std::vector<char> buf(num_needed_chars);
4805 // now try again with the proper buffer
4806 output = ryml::emit_yaml(treea, treea.root_id(), ryml::to_substr(buf), /*error_on_excess*/true);
4807 CHECK(output == ymla);
4808
4809 // it is possible to reuse the buffer and grow it as needed.
4810 // first do a blank run to find the size:
4811 output = ryml::emit_yaml(treeb, treeb.root_id(), ryml::substr{}, /*error_on_excess*/false);
4812 CHECK(output.str == nullptr);
4813 CHECK(output.len > 0);
4814 CHECK(output.len == ymlb.len);
4815 num_needed_chars = output.len;
4816 buf.resize(num_needed_chars);
4817 // now try again with the proper buffer
4818 output = ryml::emit_yaml(treeb, treeb.root_id(), ryml::to_substr(buf), /*error_on_excess*/true);
4819 CHECK(output == ymlb);
4820
4821 // there is a convenience wrapper performing the same as above:
4822 // provided to_substr() is defined for that container.
4823 output = ryml::emitrs_yaml(treeb, &buf);
4824 CHECK(output == ymlb);
4825
4826 // or you can just output a new container:
4827 // provided to_substr() is defined for that container.
4828 std::vector<char> another = ryml::emitrs_yaml<std::vector<char>>(treeb);
4829 CHECK(ryml::to_csubstr(another) == ymlb);
4830
4831 // you can also emit nested nodes:
4832 another = ryml::emitrs_yaml<std::vector<char>>(treeb[3][2]);
4833 CHECK(ryml::to_csubstr(another) == ""
4834 "more:" "\n"
4835 " vinho verde: Soalheiro" "\n"
4836 " vinho tinto: Redoma 2017" "\n"
4837 "");
4838 }
4839
4840
4841 // eg, std::string. notice this is the same code as above
4842 {
4843 // do a blank call on an empty buffer to find the required size.
4844 // no overflow will occur, and returns a substr with the size
4845 // required to output
4846 ryml::csubstr output = ryml::emit_yaml(treea, treea.root_id(), ryml::substr{}, /*error_on_excess*/false);
4847 CHECK(output.str == nullptr);
4848 CHECK(output.len > 0);
4849 size_t num_needed_chars = output.len;
4850 std::string buf;
4851 buf.resize(num_needed_chars);
4852 // now try again with the proper buffer
4853 output = ryml::emit_yaml(treea, treea.root_id(), ryml::to_substr(buf), /*error_on_excess*/true);
4854 CHECK(output == ymla);
4855
4856 // it is possible to reuse the buffer and grow it as needed.
4857 // first do a blank run to find the size:
4858 output = ryml::emit_yaml(treeb, treeb.root_id(), ryml::substr{}, /*error_on_excess*/false);
4859 CHECK(output.str == nullptr);
4860 CHECK(output.len > 0);
4861 CHECK(output.len == ymlb.len);
4862 num_needed_chars = output.len;
4863 buf.resize(num_needed_chars);
4864 // now try again with the proper buffer
4865 output = ryml::emit_yaml(treeb, treeb.root_id(), ryml::to_substr(buf), /*error_on_excess*/true);
4866 CHECK(output == ymlb);
4867
4868 // there is a convenience wrapper performing the above instructions:
4869 // provided to_substr() is defined for that container
4870 output = ryml::emitrs_yaml(treeb, &buf);
4871 CHECK(output == ymlb);
4872
4873 // or you can just output a new container:
4874 // provided to_substr() is defined for that container.
4875 std::string another = ryml::emitrs_yaml<std::string>(treeb);
4876 CHECK(ryml::to_csubstr(another) == ymlb);
4877
4878 // you can also emit nested nodes:
4879 another = ryml::emitrs_yaml<std::string>(treeb[3][2]);
4880 CHECK(ryml::to_csubstr(another) == ""
4881 "more:" "\n"
4882 " vinho verde: Soalheiro" "\n"
4883 " vinho tinto: Redoma 2017" "\n"
4884 "");
4885 }
4886}
4887
4888
4889//-----------------------------------------------------------------------------
4890
4891/** demonstrates how to emit to a stream-like structure */
4893{
4894 ryml::csubstr ymlb =
4895 "- a" "\n"
4896 "- b" "\n"
4897 "- x0: 1" "\n"
4898 " x1: 2" "\n"
4899 "- champagne: Dom Perignon" "\n"
4900 " coffee: Arabica" "\n"
4901 " more:" "\n"
4902 " vinho verde: Soalheiro" "\n"
4903 " vinho tinto: Redoma 2017" "\n"
4904 " beer:" "\n"
4905 " - Rochefort 10" "\n"
4906 " - Busch" "\n"
4907 " - Leffe Rituel" "\n"
4908 "- foo" "\n"
4909 "- bar" "\n"
4910 "- baz" "\n"
4911 "- bat" "\n"
4912 "";
4913 const ryml::Tree tree = ryml::parse_in_arena(ymlb);
4914
4915 std::string s;
4916
4917 // emit a full tree
4918 {
4919 std::stringstream ss;
4920 ss << tree; // works with any stream having .operator<<() and .write()
4921 s = ss.str();
4922 CHECK(ryml::to_csubstr(s) == ymlb);
4923 }
4924
4925 // emit a full tree as json
4926 {
4927 std::stringstream ss;
4928 ss << ryml::as_json(tree); // works with any stream having .operator<<() and .write()
4929 s = ss.str();
4930 CHECK(s ==
4931 "[" "\n"
4932 " \"a\"," "\n"
4933 " \"b\"," "\n"
4934 " {" "\n"
4935 " \"x0\": 1," "\n"
4936 " \"x1\": 2" "\n"
4937 " }," "\n"
4938 " {" "\n"
4939 " \"champagne\": \"Dom Perignon\"," "\n"
4940 " \"coffee\": \"Arabica\"," "\n"
4941 " \"more\": {" "\n"
4942 " \"vinho verde\": \"Soalheiro\"," "\n"
4943 " \"vinho tinto\": \"Redoma 2017\"" "\n"
4944 " }," "\n"
4945 " \"beer\": [" "\n"
4946 " \"Rochefort 10\"," "\n"
4947 " \"Busch\"," "\n"
4948 " \"Leffe Rituel\"" "\n"
4949 " ]" "\n"
4950 " }," "\n"
4951 " \"foo\"," "\n"
4952 " \"bar\"," "\n"
4953 " \"baz\"," "\n"
4954 " \"bat\"" "\n"
4955 "]" "\n"
4956 "");
4957 }
4958
4959 // emit a nested node
4960 {
4961 std::stringstream ss;
4962 ss << tree[3][2]; // works with any stream having .operator<<() and .write()
4963 s = ss.str();
4964 CHECK(s == ""
4965 "more:" "\n"
4966 " vinho verde: Soalheiro" "\n"
4967 " vinho tinto: Redoma 2017" "\n"
4968 "");
4969 }
4970
4971 // emit a nested node as json
4972 {
4973 std::stringstream ss;
4974 ss << ryml::as_json(tree[3][2]); // works with any stream having .operator<<() and .write()
4975 s = ss.str();
4977 "\"more\": {" "\n"
4978 " \"vinho verde\": \"Soalheiro\"," "\n"
4979 " \"vinho tinto\": \"Redoma 2017\"" "\n"
4980 "}" "\n"
4981 "");
4982 }
4983}
4984
4985
4986//-----------------------------------------------------------------------------
4987
4988/** demonstrates how to emit to a FILE* */
4990{
4991 ryml::csubstr yml = ""
4992 "- a" "\n"
4993 "- b" "\n"
4994 "- x0: 1" "\n"
4995 " x1: 2" "\n"
4996 "- champagne: Dom Perignon" "\n"
4997 " coffee: Arabica" "\n"
4998 " more:" "\n"
4999 " vinho verde: Soalheiro" "\n"
5000 " vinho tinto: Redoma 2017" "\n"
5001 " beer:" "\n"
5002 " - Rochefort 10" "\n"
5003 " - Busch" "\n"
5004 " - Leffe Rituel" "\n"
5005 "- foo" "\n"
5006 "- bar" "\n"
5007 "- baz" "\n"
5008 "- bat" "\n"
5009 "";
5010 const ryml::Tree tree = ryml::parse_in_arena(yml);
5011 // this is emitting to stdout, but of course you can pass in any
5012 // FILE* obtained from fopen()
5013 ryml::emit_yaml(tree, tree.root_id(), stdout);
5014}
5015
5016
5017//-----------------------------------------------------------------------------
5018
5019/** just like parsing into a nested node, you can also emit from a nested node. */
5021{
5022 const ryml::Tree tree = ryml::parse_in_arena(""
5023 "- a" "\n"
5024 "- b" "\n"
5025 "- x0: 1" "\n"
5026 " x1: 2" "\n"
5027 "- champagne: Dom Perignon" "\n"
5028 " coffee: Arabica" "\n"
5029 " more:" "\n"
5030 " vinho verde: Soalheiro" "\n"
5031 " vinho tinto: Redoma 2017" "\n"
5032 " beer:" "\n"
5033 " - Rochefort 10" "\n"
5034 " - Busch" "\n"
5035 " - Leffe Rituel" "\n"
5036 " - - and so" "\n"
5037 " - many other" "\n"
5038 " - wonderful beers" "\n"
5039 "- more" "\n"
5040 "- seq" "\n"
5041 "- members" "\n"
5042 "- here" "\n"
5043 "");
5044 // Let's now emit the beer node. Note that its key is also
5045 // emitted, making the result a map with a single child:
5046 CHECK(ryml::emitrs_yaml<std::string>(tree[3]["beer"]) == ""
5047 "beer:" "\n"
5048 " - Rochefort 10" "\n"
5049 " - Busch" "\n"
5050 " - Leffe Rituel" "\n"
5051 " - - and so" "\n"
5052 " - many other" "\n"
5053 " - wonderful beers" "\n"
5054 "");
5055 // You can use EmitOptions to prevent the key from being
5056 // emitted. Note how the result is now just the contents without
5057 // the key, and therefore the root is now a seq:
5058 auto without_key = ryml::EmitOptions{}.emit_nonroot_key(false);
5059 CHECK(ryml::emitrs_yaml<std::string>(tree[3]["beer"], without_key) == ""
5060 "- Rochefort 10" "\n"
5061 "- Busch" "\n"
5062 "- Leffe Rituel" "\n"
5063 "- - and so" "\n"
5064 " - many other" "\n"
5065 " - wonderful beers" "\n"
5066 "");
5067 // For sequences, the behavior is opposite. Notice how the leading
5068 // dash defaults to omit:
5069 CHECK(ryml::emitrs_yaml<std::string>(tree[3]["beer"][3]) == ""
5070 "- and so" "\n"
5071 "- many other" "\n"
5072 "- wonderful beers" "\n"
5073 "");
5074 // And likewise, you can use EmitOptions to force the dash:
5075 auto with_dash = ryml::EmitOptions{}.emit_nonroot_dash(true);
5076 CHECK(ryml::emitrs_yaml<std::string>(tree[3]["beer"][3], with_dash) == ""
5077 "- - and so" "\n" // note the added dash and indentation
5078 " - many other" "\n"
5079 " - wonderful beers" "\n"
5080 "");
5081 // Example: emit a scalar node (seq member):
5082 CHECK(ryml::emitrs_yaml<std::string>(tree[3]["beer"][0]) == "Rochefort 10");
5083 CHECK(ryml::emitrs_yaml<std::string>(tree[3]["beer"][0], with_dash) == "- Rochefort 10\n");
5084 // Example: emit a scalar node (map member):
5085 CHECK(ryml::emitrs_yaml<std::string>(tree[3]["more"][0]) == "vinho verde: Soalheiro\n");
5086 CHECK(ryml::emitrs_yaml<std::string>(tree[3]["more"][0], without_key) == "Soalheiro");
5087}
5088
5089
5090//-----------------------------------------------------------------------------
5091
5092/** query/set/modify node style to control formatting of emitted YAML
5093 * code.
5094 *
5095 * See also:
5096 * - @ref sample_parse_style()
5097 * - @ref sample_create_tree_style()
5098 * - @ref sample_style_flow_formatting()
5099 * - @ref sample_style_flow_ml_indent()
5100 * */
5102{
5103 // we will be using these helpers throughout this function
5104 auto tostr = [](ryml::ConstNodeRef n) {
5106 };
5107 auto tostr_opts = [](ryml::ConstNodeRef n, ryml::EmitOptions opts) {
5108 return ryml::emitrs_yaml<std::string>(n, opts);
5109 };
5110 // let's parse this yaml:
5111 ryml::csubstr yaml = ""
5112 "block map:" "\n"
5113 " block key: block val" "\n"
5114 "block seq:" "\n"
5115 " - block val 1" "\n"
5116 " - block val 2" "\n"
5117 " - 'quoted'" "\n"
5118 "flow map, singleline: {flow key: flow val}" "\n"
5119 "flow seq, singleline: [flow val,flow val]" "\n"
5120 "flow map, multiline: {" "\n"
5121 " flow key: flow val" "\n"
5122 " }" "\n"
5123 "flow seq, multiline: [" "\n"
5124 " flow val," "\n"
5125 " flow val" "\n"
5126 " ]" "\n"
5127 "";
5128 ryml::Tree tree = ryml::parse_in_arena(yaml);
5129 // while parsing, ryml marks parsed nodes with their original style:
5130 CHECK(tree.rootref().is_block());
5131 CHECK(tree["block map"].is_key_plain());
5132 CHECK(tree["block seq"].is_key_plain());
5133 CHECK(tree["flow map, singleline"].is_key_plain());
5134 CHECK(tree["flow seq, singleline"].is_key_plain());
5135 CHECK(tree["flow map, multiline"].is_key_plain());
5136 CHECK(tree["flow seq, multiline"].is_key_plain());
5137 CHECK(tree["block map"].is_block());
5138 CHECK(tree["block seq"].is_block());
5139 // flow is either singleline (FLOW_SL) or multiline (FLOW_ML1)
5140 CHECK(tree["flow map, singleline"].is_flow_sl());
5141 CHECK(tree["flow seq, singleline"].is_flow_sl());
5142 CHECK(tree["flow map, multiline"].is_flow_ml1());
5143 CHECK(tree["flow seq, multiline"].is_flow_ml1());
5144 // is_flow() is equivalent to (is_flow_sl() || is_flow_ml1() || is_flow_mln())
5145 CHECK(tree["flow map, singleline"].is_flow());
5146 CHECK(tree["flow seq, singleline"].is_flow());
5147 CHECK(tree["flow map, multiline"].is_flow());
5148 CHECK(tree["flow seq, multiline"].is_flow());
5149 //
5150 // since the tree nodes are marked with their original parsed
5151 // style, emitting the parsed tree will preserve the original
5152 // style (minus whitespace):
5153 //
5154 CHECK(tostr(tree) == yaml); // same as before!
5155 //
5156 // you can set/modify the style programatically!
5157 //
5158 // here are more examples.
5159 //
5160 {
5161 ryml::NodeRef n = tree["block map"]; // Let's look at one node
5162 // It looks like this originally:
5163 CHECK(tostr(n) ==
5164 "block map:\n"
5165 " block key: block val\n"
5166 "");
5167 // let's modify its style:
5168 n.set_key_style(ryml::KEY_SQUO); // scalar style: to single-quoted scalar
5169 n.set_container_style(ryml::FLOW_SL); // container style: to flow singleline
5170 // now it looks like this:
5171 CHECK(tostr(n) ==
5172 "'block map': {block key: block val}\n"
5173 "");
5174 }
5175 // next example
5176 {
5177 ryml::NodeRef n = tree["block seq"];
5178 CHECK(tostr(n) == ""
5179 "block seq:\n"
5180 " - block val 1\n"
5181 " - block val 2\n"
5182 " - 'quoted'\n"
5183 "");
5184 n.set_key_style(ryml::KEY_DQUO); // scalar style: to double-quoted scalar
5185 n[2].set_val_style(ryml::VAL_PLAIN); // scalar style: to plain
5186 n.set_container_style(ryml::FLOW_MLN); // container style: to flow multiline, N values per line
5187 CHECK(tostr(n) == ""
5188 "\"block seq\": [\n"
5189 " block val 1,block val 2,quoted\n"
5190 " ]\n");
5191 n.set_container_style(ryml::FLOW_MLN|ryml::FLOW_SPC); // force space after comma
5192 CHECK(tostr(n) == ""
5193 "\"block seq\": [\n"
5194 " block val 1, block val 2, quoted\n"
5195 " ]\n");
5196 auto maxcols20 = ryml::EmitOptions{}.max_cols(20); // set the max number of cols for FLOW_MLN
5197 CHECK(tostr_opts(n, maxcols20) == ""
5198 "\"block seq\": [\n"
5199 " block val 1, block val 2,\n"
5200 " quoted\n"
5201 " ]\n");
5202 n.set_container_style(ryml::FLOW_MLN); // no spaces now
5203 CHECK(tostr_opts(n, maxcols20) == ""
5204 "\"block seq\": [\n"
5205 " block val 1,block val 2,\n"
5206 " quoted\n"
5207 " ]\n");
5208 n.set_container_style(ryml::FLOW_SL); // to flow singleline
5209 CHECK(tostr(n) == ""
5210 "\"block seq\": [block val 1,block val 2,quoted]\n");
5211 n.set_container_style(ryml::FLOW_SL|ryml::FLOW_SPC); // now with space after comma
5212 CHECK(tostr(n) == ""
5213 "\"block seq\": [block val 1, block val 2, quoted]\n");
5214 n.set_container_style(ryml::FLOW_ML1); // to flow multiline, 1 value per line
5215 CHECK(tostr(n) == ""
5216 "\"block seq\": [\n"
5217 " block val 1,\n"
5218 " block val 2,\n"
5219 " quoted\n"
5220 " ]\n");
5221 /// @see See more details about formatting flow containers in
5222 /// @ref sample_style_flow_formatting() (below).
5223 }
5224 // next example
5225 {
5226 ryml::NodeRef n = tree["flow map, singleline"];
5227 CHECK(tostr(n) == "flow map, singleline: {flow key: flow val}\n");
5229 n["flow key"].set_val_style(ryml::VAL_LITERAL);
5230 CHECK(tostr(n) == ""
5231 "flow map, singleline:\n"
5232 " flow key: |-\n"
5233 " flow val\n"
5234 "");
5235 }
5236 // next example
5237 {
5238 ryml::NodeRef n = tree["flow map, multiline"];
5239 CHECK(tostr(n) == ""
5240 "flow map, multiline: {\n"
5241 " flow key: flow val\n"
5242 " }\n"
5243 "");
5245 CHECK(tostr(n) == ""
5246 "flow map, multiline:\n"
5247 " flow key: flow val\n"
5248 "");
5249 }
5250 // next example
5251 {
5252 ryml::NodeRef n = tree["flow seq, singleline"];
5253 CHECK(tostr(n) == "flow seq, singleline: [flow val,flow val]\n");
5258 CHECK(tostr(n) == ""
5259 "? >-\n"
5260 " flow seq, singleline\n"
5261 ":\n"
5262 " - 'flow val'\n"
5263 " - \"flow val\"\n"
5264 "");
5265 }
5266 // next example
5267 {
5268 ryml::NodeRef n = tree["flow seq, multiline"];
5269 CHECK(tostr(n) == ""
5270 "flow seq, multiline: [\n"
5271 " flow val,\n"
5272 " flow val\n"
5273 " ]\n"
5274 "");
5276 CHECK(tostr(n) == "flow seq, multiline: [flow val,flow val]\n");
5277 }
5278 // note the full tree now:
5279 CHECK(tostr(tree) != yaml);
5280 CHECK(tostr(tree) ==
5281 "'block map': {block key: block val}" "\n"
5282 "\"block seq\": [" "\n"
5283 " block val 1," "\n"
5284 " block val 2," "\n"
5285 " quoted" "\n"
5286 " ]" "\n"
5287 "flow map, singleline:" "\n"
5288 " flow key: |-" "\n"
5289 " flow val" "\n"
5290 "? >-" "\n"
5291 " flow seq, singleline" "\n"
5292 ":" "\n"
5293 " - 'flow val'" "\n"
5294 " - \"flow val\"" "\n"
5295 "flow map, multiline:" "\n"
5296 " flow key: flow val" "\n"
5297 "flow seq, multiline: [flow val,flow val]" "\n"
5298 "");
5299 // you can clear the style of single nodes:
5300 tree["block map"].clear_style();
5301 tree["block seq"].clear_style();
5302 CHECK(tostr(tree) ==
5303 "block map:" "\n"
5304 " block key: block val" "\n"
5305 "block seq:" "\n"
5306 " - block val 1" "\n"
5307 " - block val 2" "\n"
5308 " - quoted" "\n"
5309 "flow map, singleline:" "\n"
5310 " flow key: |-" "\n"
5311 " flow val" "\n"
5312 "? >-" "\n"
5313 " flow seq, singleline" "\n"
5314 ":" "\n"
5315 " - 'flow val'" "\n"
5316 " - \"flow val\"" "\n"
5317 "flow map, multiline:" "\n"
5318 " flow key: flow val" "\n"
5319 "flow seq, multiline: [flow val,flow val]" "\n"
5320 "");
5321 // you can clear the style recursively:
5322 tree.rootref().clear_style(/*recurse*/true);
5323 // when emitting nodes which have no style set, ryml will default
5324 // to block format for containers, and call
5325 // ryml::scalar_style_choose() to pick the style for each scalar
5326 // (at the cost of a scan over each scalar). Note that ryml picks
5327 // single-quoted for scalars containing commas:
5328 CHECK(tostr(tree) ==
5329 "block map:" "\n"
5330 " block key: block val" "\n"
5331 "block seq:" "\n"
5332 " - block val 1" "\n"
5333 " - block val 2" "\n"
5334 " - quoted" "\n"
5335 "flow map, singleline:" "\n"
5336 " flow key: flow val" "\n"
5337 "flow seq, singleline:" "\n"
5338 " - flow val" "\n"
5339 " - flow val" "\n"
5340 "flow map, multiline:" "\n"
5341 " flow key: flow val" "\n"
5342 "flow seq, multiline:" "\n"
5343 " - flow val" "\n"
5344 " - flow val" "\n"
5345 "");
5346 // you can set the style based on type conditions:
5347 //
5348 // eg, set a single key to single-quoted
5349 tree["block map"].set_style_conditionally(/*type_mask*/ryml::KEY,
5350 /*remflags*/ryml::KEY_STYLE,
5351 /*addflags*/ryml::KEY_SQUO,
5352 /*recurse*/false);
5353 CHECK(tostr(tree) ==
5354 "'block map':" "\n"
5355 " block key: block val" "\n"
5356 "block seq:" "\n"
5357 " - block val 1" "\n"
5358 " - block val 2" "\n"
5359 " - quoted" "\n"
5360 "flow map, singleline:" "\n"
5361 " flow key: flow val" "\n"
5362 "flow seq, singleline:" "\n"
5363 " - flow val" "\n"
5364 " - flow val" "\n"
5365 "flow map, multiline:" "\n"
5366 " flow key: flow val" "\n"
5367 "flow seq, multiline:" "\n"
5368 " - flow val" "\n"
5369 " - flow val" "\n"
5370 "");
5371 // change all keys to single-quoted:
5372 tree.rootref().set_style_conditionally(/*type_mask*/ryml::KEY,
5373 /*remflags*/ryml::KEY_STYLE,
5374 /*addflags*/ryml::KEY_SQUO,
5375 /*recurse*/true);
5376 // change all vals to double-quoted
5377 tree.rootref().set_style_conditionally(/*type_mask*/ryml::VAL,
5378 /*remflags*/ryml::VAL_STYLE,
5379 /*addflags*/ryml::VAL_DQUO,
5380 /*recurse*/true);
5381 // change all seqs to flow
5382 tree.rootref().set_style_conditionally(/*type_mask*/ryml::SEQ,
5383 /*remflags*/ryml::CONTAINER_STYLE,
5384 /*addflags*/ryml::FLOW_SL,
5385 /*recurse*/true);
5386 // change all maps to flow
5387 tree.rootref().set_style_conditionally(/*type_mask*/ryml::MAP,
5388 /*remflags*/ryml::CONTAINER_STYLE,
5389 /*addflags*/ryml::BLOCK,
5390 /*recurse*/true);
5391 // done!
5392 CHECK(tostr(tree) == ""
5393 "'block map':" "\n"
5394 " 'block key': \"block val\"" "\n"
5395 "'block seq': [\"block val 1\",\"block val 2\",\"quoted\"]" "\n"
5396 "'flow map, singleline':" "\n"
5397 " 'flow key': \"flow val\"" "\n"
5398 "'flow seq, singleline': [\"flow val\",\"flow val\"]" "\n"
5399 "'flow map, multiline':" "\n"
5400 " 'flow key': \"flow val\"" "\n"
5401 "'flow seq, multiline': [\"flow val\",\"flow val\"]" "\n"
5402 "");
5403 // you can also set a conditional style in a single node (or its branch if recurse is true):
5404 tree["flow seq, singleline"].set_style_conditionally(/*type_mask*/ryml::SEQ,
5405 /*remflags*/ryml::CONTAINER_STYLE,
5406 /*addflags*/ryml::BLOCK,
5407 /*recurse*/false);
5408 CHECK(tostr(tree) == ""
5409 "'block map':" "\n"
5410 " 'block key': \"block val\"" "\n"
5411 "'block seq': [\"block val 1\",\"block val 2\",\"quoted\"]" "\n"
5412 "'flow map, singleline':" "\n"
5413 " 'flow key': \"flow val\"" "\n"
5414 "'flow seq, singleline':" "\n"
5415 " - \"flow val\"" "\n"
5416 " - \"flow val\"" "\n"
5417 "'flow map, multiline':" "\n"
5418 " 'flow key': \"flow val\"" "\n"
5419 "'flow seq, multiline': [\"flow val\",\"flow val\"]" "\n"
5420 "");
5421 /// see also:
5422 /// - @ref ryml::scalar_style_choose_block()
5423 /// - @ref ryml::scalar_style_choose_flow()
5424 /// - @ref ryml::scalar_style_choose_json()
5425 /// - @ref ryml::scalar_style_query_squo()
5426 /// - @ref ryml::scalar_style_query_plain_flow()
5427 /// - @ref ryml::scalar_style_query_plain_block()
5428}
5429
5430
5431//-----------------------------------------------------------------------------
5432
5433/** Shows how to control formatting of flow styles. */
5435{
5436 // we will be using this helper throughout this function
5437 auto tostr = [](ryml::ConstNodeRef n, ryml::EmitOptions opts) {
5438 return ryml::emitrs_yaml<std::string>(n, opts);
5439 };
5440 auto tostr_json = [](ryml::ConstNodeRef n, ryml::EmitOptions opts) {
5441 return ryml::emitrs_json<std::string>(n, opts);
5442 };
5443 const ryml::EmitOptions emit_defaults = ryml::EmitOptions{};
5444 // let's parse this, which is in FLOW_ML1 (flow multiline, 1 value per line):
5445 ryml::csubstr yaml = ""
5446 "{" "\n"
5447 " map: {" "\n"
5448 " seq: [" "\n"
5449 " 0," "\n"
5450 " 1," "\n"
5451 " 2," "\n"
5452 " 3," "\n"
5453 " [40,41]" "\n"
5454 " ]" "\n"
5455 " }" "\n"
5456 "}" "\n"
5457 "";
5458 // note that the parser defaults to detecting multiline flow
5459 // (FLOW_ML1) containers:
5460 {
5461 const ryml::Tree tree = ryml::parse_in_arena(yaml);
5462 CHECK(tree["map"].is_flow_ml1()); // etc
5463 CHECK(tree["map"]["seq"].is_flow_ml1()); // etc
5464 CHECK(tree["map"]["seq"][4].is_flow_sl()); // etc
5465 // emitted yaml is exactly equal to parsed yaml:
5466 CHECK(tostr(tree, emit_defaults) == yaml);
5467 // json looks similar (except for the double quotes):
5468 CHECK(tostr_json(tree, emit_defaults) ==
5469 "{" "\n"
5470 " \"map\": {" "\n"
5471 " \"seq\": [" "\n"
5472 " 0," "\n"
5473 " 1," "\n"
5474 " 2," "\n"
5475 " 3," "\n"
5476 " [40,41]" "\n"
5477 " ]" "\n"
5478 " }" "\n"
5479 "}" "\n"
5480 "");
5481 }
5482 // if you prefer to shorten the emitted yaml, you can set the
5483 // parser to disable flow multiline detection. It will then pick
5484 // singleline flow (FLOW_SL) for all flow containers:
5485 {
5487 .detect_flow_ml(false);
5488 const ryml::Tree tree = ryml::parse_in_arena(yaml, opts);
5489 CHECK(tree["map"].is_flow_sl()); // etc
5490 // notice how this is smaller now:
5491 CHECK(tostr(tree, emit_defaults) ==
5492 "{map: {seq: [0,1,2,3,[40,41]]}}");
5493 // and json as well
5494 CHECK(tostr_json(tree, emit_defaults) ==
5495 "{\"map\": {\"seq\": [0,1,2,3,[40,41]]}}");
5496 // you can also force spaces everywhere without adding
5497 // FLOW_SPC in individual containers:
5498 const ryml::EmitOptions with_spaces = ryml::EmitOptions{}
5499 .force_flow_spc(true);
5500 CHECK(tostr(tree, with_spaces) ==
5501 "{map: {seq: [0, 1, 2, 3, [40, 41]]}}");
5502 // and json as well
5503 CHECK(tostr_json(tree, with_spaces) ==
5504 "{\"map\": {\"seq\": [0, 1, 2, 3, [40, 41]]}}");
5505 }
5506 // or you can still have the default detection of flow_ml, but set
5507 // it to pick FLOW_MLN (multiline, n values per line), instead of
5508 // the default FLOW_ML1 (multiline, 1 values per line)
5509 {
5512 const ryml::Tree tree = ryml::parse_in_arena(yaml, opts);
5513 CHECK(tree["map"].is_flow_mln());
5514 CHECK(tree["map"]["seq"][4].is_flow_sl()); // [40,41] is FLOW_SL
5515 CHECK(tostr(tree, emit_defaults) ==
5516 "{" "\n"
5517 " map: {" "\n"
5518 " seq: [" "\n"
5519 " 0,1,2,3,[40,41]" "\n"
5520 " ]" "\n"
5521 " }" "\n"
5522 "}" "\n");
5523 CHECK(tostr_json(tree, emit_defaults) ==
5524 "{" "\n"
5525 " \"map\": {" "\n"
5526 " \"seq\": [" "\n"
5527 " 0,1,2,3,[40,41]" "\n"
5528 " ]" "\n"
5529 " }" "\n"
5530 "}" "\n");
5531 // now with spaces:
5532 const ryml::EmitOptions with_spaces = ryml::EmitOptions{}
5533 .force_flow_spc(true);
5534 CHECK(tostr(tree, with_spaces) ==
5535 "{" "\n"
5536 " map: {" "\n"
5537 " seq: [" "\n"
5538 " 0, 1, 2, 3, [40, 41]" "\n"
5539 " ]" "\n"
5540 " }" "\n"
5541 "}" "\n");
5542 CHECK(tostr_json(tree, with_spaces) ==
5543 "{" "\n"
5544 " \"map\": {" "\n"
5545 " \"seq\": [" "\n"
5546 " 0, 1, 2, 3, [40, 41]" "\n"
5547 " ]" "\n"
5548 " }" "\n"
5549 "}" "\n");
5550 }
5551 // you can also disable indentation of both FLOW_ML1 and FLOW_MLN
5552 // (see more details in @ref sample_style_flow_ml_indent())
5553 {
5554 const ryml::EmitOptions noindent = ryml::EmitOptions{}
5555 .indent_flow_ml(false);
5556 const ryml::Tree tree = ryml::parse_in_arena(yaml);
5557 CHECK(tree["map"].is_flow_ml1());
5558 CHECK(tree["map"]["seq"][4].is_flow_sl()); // [40,41] is FLOW_SL
5559 CHECK(tostr(tree, noindent) == ""
5560 "{" "\n"
5561 "map: {" "\n"
5562 "seq: [" "\n"
5563 "0," "\n"
5564 "1," "\n"
5565 "2," "\n"
5566 "3," "\n"
5567 "[40,41]" "\n"
5568 "]" "\n"
5569 "}" "\n"
5570 "}" "\n"
5571 "");
5572 CHECK(tostr_json(tree, noindent) == ""
5573 "{" "\n"
5574 "\"map\": {" "\n"
5575 "\"seq\": [" "\n"
5576 "0," "\n"
5577 "1," "\n"
5578 "2," "\n"
5579 "3," "\n"
5580 "[40,41]" "\n"
5581 "]" "\n"
5582 "}" "\n"
5583 "}" "\n"
5584 "");
5585 }
5586 // finally, you can control the number of columns in FLOW_MLN:
5587 {
5588 // let's pick a different example to make this clearer
5589 ryml::csubstr yaml2 = ""
5590 "[" "\n"
5591 " 0, 1, 2, 3, 4, 5, 6, 7, 8, 9," "\n"
5592 " 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, " "\n"
5593 " 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, " "\n"
5594 " 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, " "\n"
5595 " 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, " "\n"
5596 " 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, " "\n"
5597 " 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, " "\n"
5598 " 70, 71, 72, 73, 74, 75, 76, 77, 78, 79 " "\n"
5599 "]";
5600 // Let's force the parser to pick FLOW_MLN instead of
5601 // FLOW_ML1. We're doing that because wrapping is only done in
5602 // FLOW_MLN and -- as their names imply -- FLOW_SL is
5603 // single-line, and FLOW_ML1 is 1 value per line.
5606 const ryml::Tree tree = ryml::parse_in_arena(yaml2, opts);
5607 CHECK(tree.rootref().type().is_flow_mln());
5608 // default max columns is 80:
5609 CHECK(tostr(tree, emit_defaults) == ""
5610 "[\n"
5611 " 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"
5612 " 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"
5613 " 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"
5614 "]\n"
5615 "");
5616 CHECK(tostr_json(tree, emit_defaults) == ""
5617 "[\n"
5618 " 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"
5619 " 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"
5620 " 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"
5621 "]\n"
5622 "");
5623 // let's try setting max columns to 40:
5624 const ryml::EmitOptions maxcols40 = ryml::EmitOptions{}
5625 .max_cols(40);
5626 CHECK(tostr(tree, maxcols40) == ""
5627 "[\n"
5628 " 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,\n"
5629 " 16,17,18,19,20,21,22,23,24,25,26,27,28,\n"
5630 " 29,30,31,32,33,34,35,36,37,38,39,40,41,\n"
5631 " 42,43,44,45,46,47,48,49,50,51,52,53,54,\n"
5632 " 55,56,57,58,59,60,61,62,63,64,65,66,67,\n"
5633 " 68,69,70,71,72,73,74,75,76,77,78,79\n"
5634 "]\n"
5635 "");
5636 CHECK(tostr_json(tree, maxcols40) == ""
5637 "[\n"
5638 " 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,\n"
5639 " 16,17,18,19,20,21,22,23,24,25,26,27,28,\n"
5640 " 29,30,31,32,33,34,35,36,37,38,39,40,41,\n"
5641 " 42,43,44,45,46,47,48,49,50,51,52,53,54,\n"
5642 " 55,56,57,58,59,60,61,62,63,64,65,66,67,\n"
5643 " 68,69,70,71,72,73,74,75,76,77,78,79\n"
5644 "]\n"
5645 "");
5646 // Note that you can globally force spaces everywhere through
5647 // the emit options:
5648 const ryml::EmitOptions with_spaces = ryml::EmitOptions{}
5649 .force_flow_spc(true);
5650 CHECK(tostr(tree, with_spaces) == ""
5651 "[\n"
5652 " 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,\n"
5653 " 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41,\n"
5654 " 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,\n"
5655 " 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79\n"
5656 "]\n"
5657 "");
5658 CHECK(tostr_json(tree, with_spaces) == ""
5659 "[\n"
5660 " 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,\n"
5661 " 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41,\n"
5662 " 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,\n"
5663 " 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79\n"
5664 "]\n"
5665 "");
5666 // and you can combine spaces with max columns:
5667 const ryml::EmitOptions maxcols40_spc = ryml::EmitOptions{}
5668 .max_cols(40)
5669 .force_flow_spc(true);
5670 CHECK(tostr(tree, maxcols40_spc) == ""
5671 "[\n"
5672 " 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,\n"
5673 " 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,\n"
5674 " 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,\n"
5675 " 32, 33, 34, 35, 36, 37, 38, 39, 40, 41,\n"
5676 " 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,\n"
5677 " 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,\n"
5678 " 62, 63, 64, 65, 66, 67, 68, 69, 70, 71,\n"
5679 " 72, 73, 74, 75, 76, 77, 78, 79\n"
5680 "]\n"
5681 "");
5682 CHECK(tostr_json(tree, maxcols40_spc) == ""
5683 "[\n"
5684 " 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,\n"
5685 " 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,\n"
5686 " 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,\n"
5687 " 32, 33, 34, 35, 36, 37, 38, 39, 40, 41,\n"
5688 " 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,\n"
5689 " 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,\n"
5690 " 62, 63, 64, 65, 66, 67, 68, 69, 70, 71,\n"
5691 " 72, 73, 74, 75, 76, 77, 78, 79\n"
5692 "]\n"
5693 "");
5694 // and you can combine spaces with max columns with no indentation:
5695 const ryml::EmitOptions maxcols40_spc_noindent = ryml::EmitOptions{}
5696 .max_cols(40)
5697 .force_flow_spc(true)
5698 .indent_flow_ml(false);
5699 CHECK(tostr(tree, maxcols40_spc_noindent) == ""
5700 "[\n"
5701 "0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,\n"
5702 "13, 14, 15, 16, 17, 18, 19, 20, 21, 22,\n"
5703 "23, 24, 25, 26, 27, 28, 29, 30, 31, 32,\n"
5704 "33, 34, 35, 36, 37, 38, 39, 40, 41, 42,\n"
5705 "43, 44, 45, 46, 47, 48, 49, 50, 51, 52,\n"
5706 "53, 54, 55, 56, 57, 58, 59, 60, 61, 62,\n"
5707 "63, 64, 65, 66, 67, 68, 69, 70, 71, 72,\n"
5708 "73, 74, 75, 76, 77, 78, 79\n"
5709 "]\n"
5710 "");
5711 CHECK(tostr_json(tree, maxcols40_spc_noindent) == ""
5712 "[\n"
5713 "0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,\n"
5714 "13, 14, 15, 16, 17, 18, 19, 20, 21, 22,\n"
5715 "23, 24, 25, 26, 27, 28, 29, 30, 31, 32,\n"
5716 "33, 34, 35, 36, 37, 38, 39, 40, 41, 42,\n"
5717 "43, 44, 45, 46, 47, 48, 49, 50, 51, 52,\n"
5718 "53, 54, 55, 56, 57, 58, 59, 60, 61, 62,\n"
5719 "63, 64, 65, 66, 67, 68, 69, 70, 71, 72,\n"
5720 "73, 74, 75, 76, 77, 78, 79\n"
5721 "]\n"
5722 "");
5723 }
5724 // Note that FLOW_SPC is /not/ detected by the parser, and that
5725 // depending on the parse options, either FLOW_ML1 or FLOW_MLN
5726 // will be used for /all/ multiline flow containers.
5727}
5728
5729
5730//-----------------------------------------------------------------------------
5731
5732/** control the indentation of emitted flow multiline containers */
5734{
5735 // we will be using this helper throughout this function
5736 auto tostr = [](ryml::ConstNodeRef n, ryml::EmitOptions opts) {
5737 return ryml::emitrs_yaml<std::string>(n, opts);
5738 };
5739 ryml::csubstr yaml = "{map: {seq: [0, 1, 2, 3, [40, 41]]}}";
5740 ryml::Tree tree = ryml::parse_in_arena(yaml);
5741 ryml::EmitOptions defaults = {};
5743 CHECK(tostr(tree, defaults) == "{map: {seq: [0,1,2,3,[40,41]]}}");
5744 // let's now set the style to FLOW_ML1 (it was FLOW_SL)
5747 tree["map"]["seq"].set_container_style(ryml::FLOW_ML1);
5748 tree["map"]["seq"][4].set_container_style(ryml::FLOW_ML1);
5749 // by default FLOW_ML1 prints one value per line, indented:
5750 CHECK(tostr(tree, defaults) ==
5751 "{" "\n"
5752 " map: {" "\n"
5753 " seq: [" "\n"
5754 " 0," "\n"
5755 " 1," "\n"
5756 " 2," "\n"
5757 " 3," "\n"
5758 " [" "\n"
5759 " 40," "\n"
5760 " 41" "\n"
5761 " ]" "\n"
5762 " ]" "\n"
5763 " }" "\n"
5764 "}" "\n"
5765 "");
5766 // if we use the noindent options, then each value is put at the
5767 // beginning of the line
5768 CHECK(tostr(tree, noindent) ==
5769 "{" "\n"
5770 "map: {" "\n"
5771 "seq: [" "\n"
5772 "0," "\n"
5773 "1," "\n"
5774 "2," "\n"
5775 "3," "\n"
5776 "[" "\n"
5777 "40," "\n"
5778 "41" "\n"
5779 "]" "\n"
5780 "]" "\n"
5781 "}" "\n"
5782 "}" "\n"
5783 "");
5784 // Note that the noindent option will safely respect any prior
5785 // indent level from enclosing block containers! For example:
5787 CHECK(tostr(tree, noindent) == ""// notice it is indented at the map level
5788 "map: {" "\n"
5789 " seq: [" "\n"
5790 " 0," "\n"
5791 " 1," "\n"
5792 " 2," "\n"
5793 " 3," "\n"
5794 " [" "\n"
5795 " 40," "\n"
5796 " 41" "\n"
5797 " ]" "\n"
5798 " ]" "\n"
5799 " }" "\n"
5800 "");
5801 // Let's set it one BLOCK level further:
5802 tree["map"].set_container_style(ryml::BLOCK);
5803 CHECK(tostr(tree, noindent) == // notice it is indented one more level
5804 "map:" "\n"
5805 " seq: [" "\n"
5806 " 0," "\n"
5807 " 1," "\n"
5808 " 2," "\n"
5809 " 3," "\n"
5810 " [" "\n"
5811 " 40," "\n"
5812 " 41" "\n"
5813 " ]" "\n"
5814 " ]" "\n"
5815 "");
5816}
5817
5818
5819//-----------------------------------------------------------------------------
5820
5821/** shows how to parse and emit JSON.
5822 *
5823 * To emit YAML parsed from JSON, see also @ref sample_style() for
5824 * info on clearing the style flags (example below). */
5826{
5827 ryml::csubstr json = ""
5828 "{" "\n"
5829 " \"doe\": \"a deer, a female deer\"," "\n"
5830 " \"ray\": \"a drop of golden sun\"," "\n"
5831 " \"me\": \"a name, I call myself\"," "\n"
5832 " \"far\": \"a long long way to go\"" "\n"
5833 "}" "\n"
5834 "";
5835 // Since JSON is a subset of YAML, parsing JSON is just the
5836 // same as YAML:
5837 ryml::Tree tree = ryml::parse_in_arena(json);
5838 // If you are sure the source is valid json, you can use the
5839 // appropriate parse_json overload, which is stricter and faster
5840 // because json has a much smaller grammar:
5841 ryml::Tree json_tree = ryml::parse_json_in_arena(json);
5842 // to emit JSON:
5844 CHECK(ryml::emitrs_json<std::string>(json_tree) == json);
5845 // to emit JSON to a stream:
5846 std::stringstream ss;
5847 ss << ryml::as_json(tree); // <- mark it like this
5848 CHECK(ss.str() == json);
5849 // Note the following limitations on the json emitter:
5850 //
5851 // - YAML streams are emitted as seqs by default. If you want to
5852 // flag this, and want it to be an error, there is a setting to
5853 // control this in EmitOptions.
5854 //
5855 // - YAML tags cannot be emitted as JSON, and are allowed only if the
5856 // relevant setting is enabled in EmitOptions.
5857 //
5858 // - Likewise, anchors and references cannot be emitted as JSON
5859 // and are allowed only if the relevant setting is enabled in
5860 // EmitOptions.
5861 //
5862
5863 // Note that when parsing JSON, ryml will set the style of each
5864 // node in the JSON. This means that if you emit YAML from a tree
5865 // parsed from JSON, it will look mostly the same as the original
5866 // JSON:
5867 CHECK(ryml::emitrs_yaml<std::string>(json_tree) == json);
5868 // If you want to avoid this, you will need to clear the style.
5869 json_tree.rootref().clear_style(); // clear the style of the map (without recursing)
5870 // note that this is now block mode. That is because when no
5871 // style is set, ryml defaults to emitting in block mode.
5872 CHECK(ryml::emitrs_yaml<std::string>(json_tree) == ""
5873 "\"doe\": \"a deer, a female deer\"" "\n"
5874 "\"ray\": \"a drop of golden sun\"" "\n"
5875 "\"me\": \"a name, I call myself\"" "\n"
5876 "\"far\": \"a long long way to go\"" "\n"
5877 "");
5878 // if you don't want the double quotes in the scalar, you can
5879 // recurse:
5880 json_tree.rootref().clear_style(/*recurse*/true);
5881 // so now when emitting you will get this:
5882 CHECK(ryml::emitrs_yaml<std::string>(json_tree) == ""
5883 "doe: a deer, a female deer" "\n"
5884 "ray: a drop of golden sun" "\n"
5885 "me: a name, I call myself" "\n"
5886 "far: a long long way to go" "\n"
5887 "");
5888 // see in particular sample_style() for more examples
5889}
5890
5891
5892//-----------------------------------------------------------------------------
5893
5894/** demonstrates usage with anchors and alias references.
5895
5896Note that dereferencing is opt-in; after parsing, you have to call
5897@ref c4::yml::Tree::resolve() explicitly if you want resolved
5898references in the tree. This method will resolve all references and
5899substitute the anchored values in place of the reference.
5900
5901The @ref c4::yml::Tree::resolve() method first does a full traversal
5902of the tree to gather all anchors and references in a separate
5903collection, then it goes through that collection to locate the names,
5904which it does by obeying the YAML standard diktat that
5905
5906 > an alias node refers to the most recent node in
5907 > the serialization having the specified anchor
5908
5909So, depending on the number of anchor/alias nodes, this is a
5910potentially expensive operation, with a best-case linear complexity
5911(from the initial traversal) and a worst-case quadratic complexity (if
5912every node has an alias/anchor). This potential cost is the reason for
5913requiring an explicit call to @ref c4::yml::Tree::resolve() */
5915{
5916 std::string unresolved = ""
5917 "base: &base" "\n"
5918 " name: Everyone has same name" "\n"
5919 "foo: &foo" "\n"
5920 " <<: *base" "\n"
5921 " age: 10" "\n"
5922 "bar: &bar" "\n"
5923 " <<: *base" "\n"
5924 " age: 20" "\n"
5925 "bill_to: &id001" "\n"
5926 " street: |-" "\n"
5927 " 123 Tornado Alley" "\n"
5928 " Suite 16" "\n"
5929 " city: East Centerville" "\n"
5930 " state: KS" "\n"
5931 "ship_to: *id001" "\n"
5932 "&keyref key: &valref val" "\n"
5933 "*valref : *keyref" "\n"
5934 "";
5935 std::string resolved = ""
5936 "base:" "\n"
5937 " name: Everyone has same name" "\n"
5938 "foo:" "\n"
5939 " name: Everyone has same name" "\n"
5940 " age: 10" "\n"
5941 "bar:" "\n"
5942 " name: Everyone has same name" "\n"
5943 " age: 20" "\n"
5944 "bill_to:" "\n"
5945 " street: |-" "\n"
5946 " 123 Tornado Alley" "\n"
5947 " Suite 16" "\n"
5948 " city: East Centerville" "\n"
5949 " state: KS" "\n"
5950 "ship_to:" "\n"
5951 " street: |-" "\n"
5952 " 123 Tornado Alley" "\n"
5953 " Suite 16" "\n"
5954 " city: East Centerville" "\n"
5955 " state: KS" "\n"
5956 "key: val" "\n"
5957 "val: key" "\n"
5958 "";
5959
5961 // by default, references are not resolved when parsing:
5962 CHECK( ! tree["base"].has_key_anchor());
5963 CHECK( tree["base"].has_val_anchor());
5964 CHECK( tree["base"].val_anchor() == "base");
5965 CHECK( tree["key"].key_anchor() == "keyref");
5966 CHECK( tree["key"].val_anchor() == "valref");
5967 CHECK( tree["*valref"].is_key_ref());
5968 CHECK( tree["*valref"].is_val_ref());
5969 CHECK( tree["*valref"].key_ref() == "valref");
5970 CHECK( tree["*valref"].val_ref() == "keyref");
5971
5972 // to resolve references, simply call tree.resolve(),
5973 // which will perform the reference instantiations:
5974 tree.resolve();
5975
5976 // all the anchors and references are substistuted and then removed:
5977 CHECK( ! tree["base"].has_key_anchor());
5978 CHECK( ! tree["base"].has_val_anchor());
5979 CHECK( ! tree["base"].has_val_anchor());
5980 CHECK( ! tree["key"].has_key_anchor());
5981 CHECK( ! tree["key"].has_val_anchor());
5982 CHECK( ! tree["val"].is_key_ref()); // notice *valref is now turned to val
5983 CHECK( ! tree["val"].is_val_ref()); // notice *valref is now turned to val
5984
5985 CHECK(tree["ship_to"]["city"].val() == "East Centerville");
5986 CHECK(tree["ship_to"]["state"].val() == "KS");
5987}
5988
5989/** demonstrates how to use the API to programatically create anchors
5990 * and aliases */
5992{
5993 // part 1: anchor/ref
5994 {
5995 ryml::Tree t;
5997 t["kanchor"].set_val("2");
5998 t["kanchor"].set_key_anchor("kanchor");
5999 t["vanchor"].set_val("3");
6000 t["vanchor"].set_val_anchor("vanchor");
6001 // to set a reference, need to call .set_val_ref()/.set_key_ref()
6002 t["kref"].set_val_ref("kanchor");
6003 t["vref"].set_val_ref("vanchor");
6004 t["nref"].set_val("*vanchor"); // NOTE: this is not set as a reference in the tree!
6006 "&kanchor kanchor: 2" "\n"
6007 "vanchor: &vanchor 3" "\n"
6008 "kref: *kanchor" "\n"
6009 "vref: *vanchor" "\n"
6010 // note that ryml emits nref with quotes to disambiguate
6011 // (because no style was set)
6012 "nref: '*vanchor'" "\n"
6013 "");
6014 t.resolve();
6016 "kanchor: 2" "\n"
6017 "vanchor: 3" "\n"
6018 "kref: kanchor" "\n"
6019 "vref: 3" "\n"
6020 // note that nref was not resolved
6021 "nref: '*vanchor'" "\n"
6022 "");
6023 }
6024
6025 // part 2: simple inheritance (ie, adding `<<: *anchor` nodes)
6026 {
6028 "orig: &orig {foo: bar, baz: bat}" "\n"
6029 "copy: {}" "\n"
6030 "notcopy: {}" "\n"
6031 "notref: {}" "\n"
6032 "");
6033 t["copy"]["<<"].set_val_ref("orig");
6034 t["notcopy"]["test"].set_val_ref("orig");
6035 t["notcopy"]["<<"].set_val_ref("orig");
6036 t["notref"]["<<"].set_val("*orig"); // not a reference! .set_val_ref() was not called
6038 "orig: &orig {foo: bar,baz: bat}" "\n"
6039 "copy: {<<: *orig}" "\n"
6040 "notcopy: {test: *orig,<<: *orig}" "\n"
6041 "notref: {<<: '*orig'}" "\n"
6042 "");
6043 t.resolve();
6045 "orig: {foo: bar,baz: bat}" "\n"
6046 "copy: {foo: bar,baz: bat}" "\n"
6047 "notcopy: {test: {foo: bar,baz: bat},foo: bar,baz: bat}" "\n"
6048 "notref: {<<: '*orig'}" "\n"
6049 "");
6050 }
6051
6052 // part 3: multiple inheritance (ie, `<<: [*ref1,*ref2,*etc]`)
6053 {
6055 "orig1: &orig1 {foo: bar}" "\n"
6056 "orig2: &orig2 {baz: bat}" "\n"
6057 "orig3: &orig3 {and: more}" "\n"
6058 "copy: {}" "\n"
6059 "");
6060 ryml::NodeRef seq = t["copy"]["<<"];
6061 seq.set_seq();
6062 seq.append_child().set_val_ref("orig1");
6063 seq.append_child().set_val_ref("orig2");
6064 seq.append_child().set_val_ref("orig3");
6066 "orig1: &orig1 {foo: bar}" "\n"
6067 "orig2: &orig2 {baz: bat}" "\n"
6068 "orig3: &orig3 {and: more}" "\n"
6069 "copy: {<<: [*orig1,*orig2,*orig3]}" "\n"
6070 "");
6071 t.resolve();
6073 "orig1: {foo: bar}" "\n"
6074 "orig2: {baz: bat}" "\n"
6075 "orig3: {and: more}" "\n"
6076 "copy: {foo: bar,baz: bat,and: more}" "\n");
6077 }
6078}
6079
6080
6081//-----------------------------------------------------------------------------
6082
6084{
6085 const std::string yaml = ""
6086 "--- !!map" "\n"
6087 "a: 0" "\n"
6088 "b: 1" "\n"
6089 "--- !map" "\n"
6090 "a: b" "\n"
6091 "--- !!seq" "\n"
6092 "- a" "\n"
6093 "- b" "\n"
6094 "--- !!str a b" "\n"
6095 "--- !!str 'a: b'" "\n"
6096 "---" "\n"
6097 "!!str a: b" "\n"
6098 "--- !!set" "\n"
6099 "? a" "\n"
6100 "? b" "\n"
6101 "--- !!set" "\n"
6102 "a:" "\n"
6103 "--- !!seq" "\n"
6104 "- !!int 0" "\n"
6105 "- !!str 1" "\n"
6106 "";
6108 const ryml::ConstNodeRef root = tree.rootref();
6109 CHECK(root.is_stream());
6110 CHECK(root.num_children() == 9);
6111 for(ryml::ConstNodeRef doc : root.children())
6112 CHECK(doc.is_doc());
6113 // tags are kept verbatim from the source:
6114 CHECK(root[0].has_val_tag());
6115 CHECK(root[0].val_tag() == "!!map"); // valid only if the node has a val tag
6116 CHECK(root[1].val_tag() == "!map");
6117 CHECK(root[2].val_tag() == "!!seq");
6118 CHECK(root[3].val_tag() == "!!str");
6119 CHECK(root[4].val_tag() == "!!str");
6120 CHECK(root[5]["a"].has_key_tag());
6121 CHECK(root[5]["a"].key_tag() == "!!str"); // valid only if the node has a key tag
6122 CHECK(root[6].val_tag() == "!!set");
6123 CHECK(root[7].val_tag() == "!!set");
6124 CHECK(root[8].val_tag() == "!!seq");
6125 CHECK(root[8][0].val_tag() == "!!int");
6126 CHECK(root[8][1].val_tag() == "!!str");
6127 // ryml also provides a complete toolbox to deal with tags.
6128 // there is an enumeration for the standard YAML tags:
6129 CHECK(ryml::to_tag("!map") == ryml::TAG_NONE);
6130 CHECK(ryml::to_tag("!!map") == ryml::TAG_MAP);
6131 CHECK(ryml::to_tag("!!seq") == ryml::TAG_SEQ);
6132 CHECK(ryml::to_tag("!!str") == ryml::TAG_STR);
6133 CHECK(ryml::to_tag("!!int") == ryml::TAG_INT);
6134 CHECK(ryml::to_tag("!!set") == ryml::TAG_SET);
6135 // given a tag enum, you can fetch the short tag string:
6137 CHECK(ryml::from_tag(ryml::TAG_MAP) == "!!map");
6138 CHECK(ryml::from_tag(ryml::TAG_SEQ) == "!!seq");
6139 CHECK(ryml::from_tag(ryml::TAG_STR) == "!!str");
6140 CHECK(ryml::from_tag(ryml::TAG_INT) == "!!int");
6141 CHECK(ryml::from_tag(ryml::TAG_SET) == "!!set");
6142 // you can also fetch the long tag string:
6144 CHECK(ryml::from_tag_long(ryml::TAG_MAP) == "<tag:yaml.org,2002:map>");
6145 CHECK(ryml::from_tag_long(ryml::TAG_SEQ) == "<tag:yaml.org,2002:seq>");
6146 CHECK(ryml::from_tag_long(ryml::TAG_STR) == "<tag:yaml.org,2002:str>");
6147 CHECK(ryml::from_tag_long(ryml::TAG_INT) == "<tag:yaml.org,2002:int>");
6148 CHECK(ryml::from_tag_long(ryml::TAG_SET) == "<tag:yaml.org,2002:set>");
6149 // and likewise:
6150 CHECK(ryml::to_tag("!map") == ryml::TAG_NONE);
6151 CHECK(ryml::to_tag("<tag:yaml.org,2002:map>") == ryml::TAG_MAP);
6152 CHECK(ryml::to_tag("<tag:yaml.org,2002:seq>") == ryml::TAG_SEQ);
6153 CHECK(ryml::to_tag("<tag:yaml.org,2002:str>") == ryml::TAG_STR);
6154 CHECK(ryml::to_tag("<tag:yaml.org,2002:int>") == ryml::TAG_INT);
6155 CHECK(ryml::to_tag("<tag:yaml.org,2002:set>") == ryml::TAG_SET);
6156 // to normalize a tag as much as possible, use normalize_tag():
6157 CHECK(ryml::normalize_tag("!!map") == "!!map");
6158 CHECK(ryml::normalize_tag("!<tag:yaml.org,2002:map>") == "!!map");
6159 CHECK(ryml::normalize_tag("<tag:yaml.org,2002:map>") == "!!map");
6160 CHECK(ryml::normalize_tag("tag:yaml.org,2002:map") == "!!map");
6161 CHECK(ryml::normalize_tag("!<!!map>") == "<!!map>");
6162 CHECK(ryml::normalize_tag("!map") == "!map");
6163 CHECK(ryml::normalize_tag("!my!foo") == "!my!foo");
6164 // and also for the long form:
6165 CHECK(ryml::normalize_tag_long("!!map") == "<tag:yaml.org,2002:map>");
6166 CHECK(ryml::normalize_tag_long("!<tag:yaml.org,2002:map>") == "<tag:yaml.org,2002:map>");
6167 CHECK(ryml::normalize_tag_long("<tag:yaml.org,2002:map>") == "<tag:yaml.org,2002:map>");
6168 CHECK(ryml::normalize_tag_long("tag:yaml.org,2002:map") == "<tag:yaml.org,2002:map>");
6169 CHECK(ryml::normalize_tag_long("!<!!map>") == "<!!map>");
6170 CHECK(ryml::normalize_tag_long("!map") == "!map");
6171 // The tree provides the following methods applying to every node
6172 // with a key and/or val tag:
6173 ryml::Tree normalized_tree = tree;
6174 normalized_tree.normalize_tags(); // normalize all tags in short form
6175 CHECK(ryml::emitrs_yaml<std::string>(normalized_tree) == ""
6176 "--- !!map" "\n"
6177 "a: 0" "\n"
6178 "b: 1" "\n"
6179 "--- !map" "\n"
6180 "a: b" "\n"
6181 "--- !!seq" "\n"
6182 "- a" "\n"
6183 "- b" "\n"
6184 "--- !!str a b" "\n"
6185 "--- !!str 'a: b'" "\n"
6186 "---" "\n"
6187 "!!str a: b" "\n"
6188 "--- !!set" "\n"
6189 "a: " "\n"
6190 "b: " "\n"
6191 "--- !!set" "\n"
6192 "a: " "\n"
6193 "--- !!seq" "\n"
6194 "- !!int 0" "\n"
6195 "- !!str 1" "\n"
6196 "");
6197 ryml::Tree normalized_tree_long = tree;
6198 normalized_tree_long.normalize_tags_long(); // normalize all tags in short form
6199 CHECK(ryml::emitrs_yaml<std::string>(normalized_tree_long) == ""
6200 "--- !<tag:yaml.org,2002:map>" "\n"
6201 "a: 0" "\n"
6202 "b: 1" "\n"
6203 "--- !map" "\n"
6204 "a: b" "\n"
6205 "--- !<tag:yaml.org,2002:seq>" "\n"
6206 "- a" "\n"
6207 "- b" "\n"
6208 "--- !<tag:yaml.org,2002:str> a b" "\n"
6209 "--- !<tag:yaml.org,2002:str> 'a: b'" "\n"
6210 "---" "\n"
6211 "!<tag:yaml.org,2002:str> a: b" "\n"
6212 "--- !<tag:yaml.org,2002:set>" "\n"
6213 "a: " "\n"
6214 "b: " "\n"
6215 "--- !<tag:yaml.org,2002:set>" "\n"
6216 "a: " "\n"
6217 "--- !<tag:yaml.org,2002:seq>" "\n"
6218 "- !<tag:yaml.org,2002:int> 0" "\n"
6219 "- !<tag:yaml.org,2002:str> 1" "\n"
6220 "");
6221}
6222
6223
6224//-----------------------------------------------------------------------------
6225
6227{
6228 const std::string yaml = ""
6229 "%TAG !m! !my-" "\n"
6230 "--- # Bulb here" "\n"
6231 "!m!light fluorescent" "\n"
6232 "..." "\n"
6233 "%TAG !m! !meta-" "\n"
6234 "--- # Color here" "\n"
6235 "!m!light green" "\n"
6236 "";
6237 // tags are not resolved by default:
6240 "%TAG !m! !my-" "\n"
6241 "--- !m!light fluorescent" "\n"
6242 "..." "\n"
6243 "%TAG !m! !meta-" "\n"
6244 "--- !m!light green" "\n"
6245 "");
6246 // Use Tree::resolve_tags() to accomplish this in an existing
6247 // tree:
6248 ryml::TagCache tag_cache; // reduces memory requirements by reusing resolved tags
6249 tree.resolve_tags(tag_cache);
6251 "%TAG !m! !my-" "\n"
6252 "--- !<!my-light> fluorescent" "\n"
6253 "..." "\n"
6254 "%TAG !m! !meta-" "\n"
6255 "--- !<!meta-light> green" "\n"
6256 "");
6257 // You can also Use ParserOptions to force resolution of tags
6258 // while parsing:
6260 ryml::Tree resolved_tree = ryml::parse_in_arena(ryml::to_csubstr(yaml), opts);
6261 CHECK(ryml::emitrs_yaml<std::string>(resolved_tree) == ""
6262 "%TAG !m! !my-" "\n"
6263 "--- !<!my-light> fluorescent" "\n"
6264 "..." "\n"
6265 "%TAG !m! !meta-" "\n"
6266 "--- !<!meta-light> green" "\n"
6267 "");
6268 // see also tree.normalize_tags()
6269 // see also tree.normalize_tags_long()
6270}
6271
6272
6273//-----------------------------------------------------------------------------
6274
6276{
6277 ryml::csubstr yml = ""
6278 "---" "\n"
6279 "a: 0" "\n"
6280 "b: 1" "\n"
6281 "---" "\n"
6282 "c: 2" "\n"
6283 "d: 3" "\n"
6284 "---" "\n"
6285 "- 4" "\n"
6286 "- 5" "\n"
6287 "- 6" "\n"
6288 "- 7" "\n"
6289 "";
6292
6293 // iteration through docs
6294 {
6295 // using the node API
6296 const ryml::ConstNodeRef stream = tree.rootref();
6297 CHECK(stream.is_root());
6298 CHECK(stream.is_stream());
6299 CHECK(!stream.is_doc());
6300 CHECK(stream.num_children() == 3);
6301 for(const ryml::ConstNodeRef doc : stream.children())
6302 CHECK(doc.is_doc());
6303 CHECK(tree.docref(0).id() == stream.child(0).id());
6304 CHECK(tree.docref(1).id() == stream.child(1).id());
6305 CHECK(tree.docref(2).id() == stream.child(2).id());
6306 // equivalent: using the lower level index API
6307 const ryml::id_type stream_id = tree.root_id();
6308 CHECK(tree.is_root(stream_id));
6309 CHECK(tree.is_stream(stream_id));
6310 CHECK(!tree.is_doc(stream_id));
6311 CHECK(tree.num_children(stream_id) == 3);
6312 for(ryml::id_type doc_id = tree.first_child(stream_id);
6313 doc_id != ryml::NONE;
6314 doc_id = tree.next_sibling(stream_id))
6315 CHECK(tree.is_doc(doc_id));
6316 CHECK(tree.doc(0) == tree.child(stream_id, 0));
6317 CHECK(tree.doc(1) == tree.child(stream_id, 1));
6318 CHECK(tree.doc(2) == tree.child(stream_id, 2));
6319
6320 // using the node API
6321 CHECK(stream[0].is_doc());
6322 CHECK(stream[0].is_map());
6323 CHECK(stream[0]["a"].val() == "0");
6324 CHECK(stream[0]["b"].val() == "1");
6325 // equivalent: using the index API
6326 const ryml::id_type doc0_id = tree.first_child(stream_id);
6327 CHECK(tree.is_doc(doc0_id));
6328 CHECK(tree.is_map(doc0_id));
6329 CHECK(tree.val(tree.find_child(doc0_id, "a")) == "0");
6330 CHECK(tree.val(tree.find_child(doc0_id, "b")) == "1");
6331
6332 // using the node API
6333 CHECK(stream[1].is_doc());
6334 CHECK(stream[1].is_map());
6335 CHECK(stream[1]["c"].val() == "2");
6336 CHECK(stream[1]["d"].val() == "3");
6337 // equivalent: using the index API
6338 const ryml::id_type doc1_id = tree.next_sibling(doc0_id);
6339 CHECK(tree.is_doc(doc1_id));
6340 CHECK(tree.is_map(doc1_id));
6341 CHECK(tree.val(tree.find_child(doc1_id, "c")) == "2");
6342 CHECK(tree.val(tree.find_child(doc1_id, "d")) == "3");
6343
6344 // using the node API
6345 CHECK(stream[2].is_doc());
6346 CHECK(stream[2].is_seq());
6347 CHECK(stream[2][0].val() == "4");
6348 CHECK(stream[2][1].val() == "5");
6349 CHECK(stream[2][2].val() == "6");
6350 CHECK(stream[2][3].val() == "7");
6351 // equivalent: using the index API
6352 const ryml::id_type doc2_id = tree.next_sibling(doc1_id);
6353 CHECK(tree.is_doc(doc2_id));
6354 CHECK(tree.is_seq(doc2_id));
6355 CHECK(tree.val(tree.child(doc2_id, 0)) == "4");
6356 CHECK(tree.val(tree.child(doc2_id, 1)) == "5");
6357 CHECK(tree.val(tree.child(doc2_id, 2)) == "6");
6358 CHECK(tree.val(tree.child(doc2_id, 3)) == "7");
6359 }
6360
6361 // Note: ryml emits streams as a JSON seq by default:
6363 "[" "\n"
6364 " {" "\n"
6365 " \"a\": 0," "\n"
6366 " \"b\": 1" "\n"
6367 " }," "\n"
6368 " {" "\n"
6369 " \"c\": 2," "\n"
6370 " \"d\": 3" "\n"
6371 " }," "\n"
6372 " [" "\n"
6373 " 4," "\n"
6374 " 5," "\n"
6375 " 6," "\n"
6376 " 7" "\n"
6377 " ]" "\n"
6378 "]\n"
6379 "");
6380 // ... but you can use EmitOptions{} to set the emitter to fail if
6381 // it finds a stream:
6382 {
6383 ScopedErrorHandlerExample errh; // calls ryml::set_callbacks()
6385 CHECK(err_tree.callbacks() == errh.callbacks());
6386 auto err_opts = ryml::EmitOptions{}.json_err_on_stream(true);
6387 CHECK(errh.check_error_occurs([&]{
6388 return ryml::emitrs_json<std::string>(err_tree, err_opts);
6389 }));
6390 // in which case, you can avoid the error by emitting the
6391 // documents one-by-one:
6392 const std::string expected_json[] = {
6393 "{" "\n"
6394 " \"a\": 0," "\n"
6395 " \"b\": 1" "\n"
6396 "}" "\n"
6397 ,
6398 "{" "\n"
6399 " \"c\": 2," "\n"
6400 " \"d\": 3" "\n"
6401 "}" "\n"
6402 ,
6403 "[" "\n"
6404 " 4," "\n"
6405 " 5," "\n"
6406 " 6," "\n"
6407 " 7" "\n"
6408 "]" "\n"
6409 };
6410 ryml::id_type count = 0;
6411 const ryml::ConstNodeRef stream = err_tree;
6412 CHECK(stream.num_children() == (ryml::id_type)C4_COUNTOF(expected_json));
6413 for(ryml::ConstNodeRef doc : stream.children())
6414 CHECK(ryml::emitrs_json<std::string>(doc, err_opts) == expected_json[count++]);
6415 }
6416}
6417
6418
6419//-----------------------------------------------------------------------------
6420
6421// To avoid imposing a particular type of error handling, ryml uses
6422// error handler callbacks. This enables users to use exceptions, or
6423// setjmp()/longjmp(), or plain calls to abort(), as they see fit.
6424//
6425// However, it is important to note that the error callbacks must never
6426// return to the caller! Otherwise, an infinite loop or program crash
6427// will likely occur.
6428//
6429// For this reason, to recover from an error when exceptions are disabled,
6430// then a non-local jump must be performed using setjmp()/longjmp().
6431// The code below demonstrates both flows.
6432//
6433// ryml provides default error handlers, which call
6434// std::abort(). You can use the cmake option and the macro
6435// RYML_DEFAULT_CALLBACK_USES_EXCEPTIONS to have the default error
6436// handler throw an exception instead.
6437
6438/** demonstrates how to set a custom error handler for ryml */
6440{
6441 ErrorHandlerExample errh; // browse the implementation of this
6442 // class to understand more details
6443 errh.check_disabled();
6444 // set the global error handlers. Note the error callbacks must
6445 // never return: they must either throw an exception, use setjmp()
6446 // and longjmp(), or abort. Otherwise, the parser will enter into
6447 // an infinite loop, or the program may crash.
6449 errh.check_enabled();
6450 CHECK(errh.check_error_occurs([&]{
6451 ryml::Tree tree = ryml::parse_in_arena("errorhandler.yml", "[a: b\n}");
6452 }));
6453 ryml::set_callbacks(errh.original_callbacks); // restore defaults.
6454 errh.check_disabled();
6455}
6456
6457
6458//-----------------------------------------------------------------------------
6459
6461{
6462 auto cause_basic_error = []{
6463 ryml::Tree t;
6464 ryml::csubstr tag_handle = {}, tag_prefix = {}; // invalid, not filled
6465 t.add_tag_directive(tag_handle, tag_prefix, 0);
6466 };
6467 {
6468 ScopedErrorHandlerExample errh; // set the example callbacks (scoped)
6469 CHECK(errh.check_error_occurs(cause_basic_error));
6470 }
6471#ifdef RYML_WITH_EXCEPTIONS_
6472 bool gotit = false;
6473 try
6474 {
6475 cause_basic_error();
6476 }
6477 catch(ryml::ExceptionBasic const& exc)
6478 {
6479 gotit = true;
6480 ryml::csubstr msg = ryml::to_csubstr(exc.what());
6482 CHECK(!msg.empty());
6483 }
6484 CHECK(gotit);
6485#endif
6486}
6487
6488
6490{
6491 ryml::csubstr ymlsrc = ""
6492 "{" "\n"
6493 " a: b" "\n"
6494 " [" "\n"
6495 "";
6496 ryml::csubstr ymlfile = "file.yml";
6497 auto cause_parse_error = [&]{
6498 return ryml::parse_in_arena(ymlfile, ymlsrc);
6499 };
6500 // the YAML in ymlsrc must cause a parse error while it is being
6501 // parsed. We use our error handler to catch that error, and save
6502 // the error info:
6504 {
6506 CHECK(errh.check_error_occurs(cause_parse_error));
6507 // the handler in errh saves the error info in itself. Let's
6508 // use that to see the messages we get.
6509 //
6510 // this message is the short message passed into the parse
6511 // error handler:
6512 CHECK(errh.saved_msg_short == "invalid character: '['");
6513 // this message was created inside the handler, by calling
6514 // ryml::err_parse_format():
6515 CHECK(ryml::to_csubstr(errh.saved_msg_full).begins_with("file.yml:3: col=4 (12B): ERROR: [parse] invalid character: '['"));
6516 // If you keep the YAML source buffer around, you can also use
6517 // it to create/print a larger error message showing the
6518 // YAML source code context which causes the error:
6519 std::string msg_ctx = errh.saved_msg_full + "\n";
6521 msg_ctx.append(s.str, s.len);
6522 }, errh.saved_parse_loc, ymlsrc, "err");
6523 CHECK(ryml::to_csubstr(msg_ctx).begins_with("file.yml:3: col=4 (12B): ERROR: [parse] invalid character: '['"));
6524 CHECK(ryml::to_csubstr(msg_ctx).ends_with(
6525 "file.yml:3: col=4 (12B): err:" "\n"
6526 "err:" "\n"
6527 "err: [" "\n"
6528 "err: |" "\n"
6529 "err: (here)" "\n"
6530 "err:" "\n"
6531 "err: see region:" "\n"
6532 "err:" "\n"
6533 "err: {" "\n"
6534 "err: a: b" "\n"
6535 "err: [" "\n"
6536 "err: |" "\n"
6537 "err: (here)" "\n"
6538 ""));
6539 //
6540 // Let's now check the location (see the message above):
6541 CHECK(errh.saved_parse_loc.name == ymlfile);
6542 CHECK(errh.saved_parse_loc.line == 3);
6543 CHECK(errh.saved_parse_loc.col == 4);
6544 CHECK(errh.saved_parse_loc.offset == 12);
6545 CHECK(errh.saved_parse_loc.offset <= ymlsrc.len);
6546 // ... and this is the location in the ryml source code file where
6547 // this error was found:
6549 CHECK(errh.saved_basic_loc.line > 0);
6550 CHECK(errh.saved_basic_loc.col > 0);
6551 CHECK(errh.saved_basic_loc.offset > 0);
6553 }
6554 // A parse error is also a basic error. If no parse error handler
6555 // is set, then ryml falls back to a basic error:
6556 {
6557 ryml::Callbacks cb = errh.callbacks();
6558 cb.m_error_parse = nullptr;
6560 CHECK(ryml::get_callbacks().m_error_parse == nullptr);
6561 CHECK(errh.check_error_occurs(cause_parse_error));
6562 // we got a basic error instead of a parse error:
6563 CHECK(errh.saved_msg_short == "invalid character: '['");
6564 // notice that the full message now displays this as a basic
6565 // error:
6566 CHECK(errh.saved_msg_full == "file.yml:3: col=4 (12B): ERROR: [basic] invalid character: '['");
6567 // the yml location is now in the location saved from the basic error
6568 CHECK(errh.saved_basic_loc.name == ymlfile);
6569 CHECK(errh.saved_basic_loc.line == 3);
6570 CHECK(errh.saved_basic_loc.col == 4);
6571 CHECK(errh.saved_basic_loc.offset == 12);
6572 CHECK(errh.saved_basic_loc.offset <= ymlsrc.len);
6578 }
6579#ifdef RYML_WITH_EXCEPTIONS_
6580 bool gotit = false;
6581 try
6582 {
6583 cause_parse_error();
6584 }
6585 catch(ryml::ExceptionParse const& exc)
6586 {
6587 gotit = true;
6588 ryml::csubstr msg = ryml::to_csubstr(exc.what());
6589 CHECK(exc.errdata_parse.ymlloc.name == ymlfile);
6590 CHECK(exc.errdata_parse.ymlloc.line == 3);
6591 CHECK(exc.errdata_parse.ymlloc.col == 4);
6592 CHECK(exc.errdata_parse.ymlloc.offset == 12);
6593 CHECK(exc.errdata_parse.ymlloc.offset <= ymlsrc.len);
6594 // the message saved in the exception is just the concrete error description:
6595 CHECK(msg == "invalid character: '['");
6596 // to print richer error messages, ryml provides helpers to
6597 // format that description into a complete error message,
6598 // containing location and source context indication:
6599 std::string full;
6600 auto dumpfn = [&full](ryml::csubstr s) { full.append(s.str, s.len); };
6601 ryml::err_parse_format(dumpfn, msg, exc.errdata_parse);
6602 full += '\n';
6603 ryml::location_format_with_context(dumpfn, exc.errdata_parse.ymlloc, ymlsrc, "err", 3);
6604 CHECK(ryml::to_csubstr(full).begins_with("file.yml:3: col=4 (12B): ERROR: [parse] invalid character: '['"));
6605 CHECK(ryml::to_csubstr(full).ends_with(
6606 "file.yml:3: col=4 (12B): err:" "\n"
6607 "err:" "\n"
6608 "err: [" "\n"
6609 "err: |" "\n"
6610 "err: (here)" "\n"
6611 "err:" "\n"
6612 "err: see region:" "\n"
6613 "err:" "\n"
6614 "err: {" "\n"
6615 "err: a: b" "\n"
6616 "err: [" "\n"
6617 "err: |" "\n"
6618 "err: (here)" "\n"
6619 ""));
6620 }
6621 CHECK(gotit);
6622 gotit = false;
6623 try
6624 {
6625 cause_parse_error();
6626 }
6627 catch(ryml::ExceptionBasic const& exc) // use references! don't slice the exception
6628 {
6629 gotit = true;
6630 ryml::csubstr msg = ryml::to_csubstr(exc.what());
6632 CHECK(!msg.empty());
6633 }
6634 CHECK(gotit);
6635#endif
6636}
6637
6638
6639/** Visit errors happen when an error is triggered while reading from
6640 * a node. */
6642{
6643 ryml::csubstr ymlfile = "file.yml";
6644 ryml::csubstr ymlsrc = "float: 123.456";
6646 {
6648 ryml::Tree tree = ryml::parse_in_arena(ymlfile, ymlsrc);
6649 CHECK(errh.check_error_occurs([&]{
6650 int intval = 0;
6651 tree["float"].load(&intval); // cannot deserialize 123.456 to int
6652 }));
6653 // the handler in errh saves the error info in itself. Let's
6654 // use that to see the messages we get.
6655 //
6656 // this message is the short message passed into the visit error
6657 CHECK(errh.saved_msg_short == "could not deserialize node");
6658 // this message was created inside the handler, by calling
6659 // ryml::err_visit_format():
6660 CHECK(ryml::csubstr::npos != ryml::to_csubstr(errh.saved_msg_full).find("ERROR: [visit] could not deserialize node"));
6661 // The location of the visit error is of the C++ source file where
6662 // the error was detected -- NOT of the YAML source file:
6663 CHECK(errh.saved_basic_loc.name != ymlfile);
6664 // However, note that the tree and node id are available:
6665 CHECK(errh.saved_visit_tree == &tree);
6666 CHECK(errh.saved_visit_id == tree["float"].id());
6667 // see sample_error_visit_location() for an example on how
6668 // to extract the location.
6669 }
6670 // visit errors also fall back to basic errors when the visit
6671 // handler is not set (similar to the behavior of ExceptionVisit):
6672 {
6673 ryml::Callbacks cb = errh.callbacks();
6674 cb.m_error_visit = nullptr;
6676 CHECK(ryml::get_callbacks().m_error_visit == nullptr);
6677 ryml::Tree tree = ryml::parse_in_arena(ymlfile, ymlsrc);
6678 CHECK(errh.check_error_occurs([&]{
6679 int intval = 0;
6680 tree["float"].load(&intval); // cannot deserialize 123.456 to int
6681 }));
6682 // we got a basic error instead of a visit error:
6683 CHECK(errh.saved_msg_short == "could not deserialize node");
6684 // notice that the full message now displays this as a basic
6685 // error:
6686 CHECK(ryml::csubstr::npos != ryml::to_csubstr(errh.saved_msg_full).find("ERROR: [basic] could not deserialize node"));
6687 // the tree and id are not set, because this was called as a basic error
6688 CHECK(errh.saved_visit_tree == nullptr);
6691 }
6692#ifdef RYML_WITH_EXCEPTIONS_
6693 // when using the default ryml callbacks (see
6694 // RYML_NO_DEFAULT_CALLBACKS), and
6695 // RYML_DEFAULT_CALLBACK_USES_EXCEPTIONS is defined, the ryml
6696 // parse handler throws an exception of type ryml::ExceptionVisit,
6697 // which is derived from ryml::ExceptionBasic.
6698 {
6699 const ryml::Tree tree = ryml::parse_in_arena(ymlfile, ymlsrc);
6700 bool gotit = false;
6701 try
6702 {
6703 int intval = 0;
6704 tree["float"].load(&intval); // cannot deserialize 123.456 to int
6705 }
6706 catch(ryml::ExceptionVisit const& exc)
6707 {
6708 gotit = true;
6709 ryml::csubstr msg = ryml::to_csubstr(exc.what());
6711 CHECK(exc.errdata_visit.tree == &tree);
6712 CHECK(exc.errdata_visit.node == tree["float"].id());
6713 CHECK(!msg.empty());
6714 }
6715 CHECK(gotit);
6716 }
6717 // you can also catch the exception as its base,
6718 // ryml::ExceptionBasic:
6719 {
6720 const ryml::Tree tree = ryml::parse_in_arena(ymlfile, ymlsrc);
6721 bool gotit = false;
6722 try
6723 {
6724 int intval = 0;
6725 tree["float"].load(&intval); // cannot deserialize 123.456 to int
6726 }
6727 catch(ryml::ExceptionBasic const& exc) // use references! don't slice the exception
6728 {
6729 gotit = true;
6730 ryml::csubstr msg = ryml::to_csubstr(exc.what());
6732 CHECK(!msg.empty());
6733 }
6734 CHECK(gotit);
6735 }
6736#endif
6737}
6738
6739
6740/** It is possible to obtain the YAML location from a visit
6741 * error: when the tree is obtained from parsing YAML, the messages
6742 * may be enriched by using a parser set to track the locations. See
6743 * @ref sample_location_tracking() for more details on how to use
6744 * locations. */
6746{
6748 // we will use locations to show the YAML source context of the
6749 // node where the visit error was triggered. This is a very
6750 // convenient feature to show detailed messages when deserializing
6751 // data read from a file (but do note this is opt-in, and it is
6752 // not mandatory). See sample_location_tracking() for more details
6753 // on location tracking.
6755 ryml::EventHandlerTree evt_handler{};
6756 ryml::Parser parser(&evt_handler, opts);
6757 ryml::csubstr ymlfile = "file.yml";
6758 ryml::csubstr ymlsrc = ""
6759 "foo: bar" "\n"
6760 "char: a" "\n"
6761 "int: a" "\n"
6762 "float: 123.456" "\n"
6763 "";
6764 const ryml::Tree tree = ryml::parse_in_arena(&parser, ymlfile, ymlsrc);
6765 // This function will cause a visit error when being called:
6766 auto cause_visit_error = [&]{
6767 int intval = 0;
6768 tree["float"].load(&intval); // cannot deserialize 123.456 to int
6769 };
6770 // Like with the parse error, we will use our error handler to
6771 // catch that visit error, and save the error info:
6772 CHECK(evt_handler.callbacks() == errh.callbacks());
6773 CHECK(parser.callbacks() == errh.callbacks());
6774 CHECK(tree.callbacks() == errh.callbacks());
6775 {
6776 CHECK(errh.check_error_occurs(cause_visit_error));
6777 // the handler in errh saves the error info in itself. Let's
6778 // use that to see the messages we get.
6779 //
6780 // this message is the short message passed into the visit error
6781 CHECK(errh.saved_msg_short == "could not deserialize node");
6782 // this message was created inside the handler, by calling
6783 // ryml::err_visit_format():
6784 CHECK(ryml::csubstr::npos != ryml::to_csubstr(errh.saved_msg_full).find("ERROR: [visit] could not deserialize node"));
6785 // The location of the visit error is of the C++ source file where
6786 // the error was detected -- NOT of the YAML source file:
6787 CHECK(errh.saved_basic_loc.name != ymlfile);
6788 // However, note that the tree and node id are available:
6789 CHECK(errh.saved_visit_tree == &tree);
6790 CHECK(errh.saved_visit_id == tree["float"].id());
6791 // ... which we can use to get the location in the YAML source
6792 // from the parser (but see @ref sample_location_tracking()):
6793 ryml::Location ymlloc = errh.saved_visit_tree->location(parser, errh.saved_visit_id);
6794 CHECK(ymlloc.name == ymlfile);
6795 // In turn, we can use format_location_context() to
6796 // print/create an error message pointing at the YAML source
6797 // code:
6798 std::string msg = errh.saved_msg_full;
6800 msg.append(s.str, s.len);
6801 }, ymlloc, ymlsrc, "err", /*number of lines to show before the error*/3);
6802 CHECK(ryml::to_csubstr(msg).ends_with(
6803 "file.yml:3: col=0 (24B): err:" "\n"
6804 "err:" "\n"
6805 "err: float: 123.456" "\n"
6806 "err: |" "\n"
6807 "err: (here)" "\n"
6808 "err:" "\n"
6809 "err: see region:" "\n"
6810 "err:" "\n"
6811 "err: foo: bar" "\n"
6812 "err: char: a" "\n"
6813 "err: int: a" "\n"
6814 "err: float: 123.456" "\n"
6815 "err: |" "\n"
6816 "err: (here)" "\n"
6817 ""));
6818 }
6819}
6820
6821
6822//-----------------------------------------------------------------------------
6823
6824// Please note the following about the use of custom allocators with
6825// ryml. Due to [the static initialization order
6826// fiasco](https://en.cppreference.com/w/cpp/language/siof), if you
6827// use static ryml trees or parsers, you need to make sure that their
6828// callbacks have the same lifetime. So you can't use ryml's default
6829// callbacks structure, as it is declared in a ryml file, and the standard
6830// provides no guarantee on the relative initialization order, such
6831// that it is constructed before and destroyed after your
6832// variables. In fact you are pretty much guaranteed to see this
6833// fail. So please carefully consider your choices, and ponder
6834// whether you really need to use ryml static trees and parsers. If
6835// you do need this, then you will need to declare and use a ryml
6836// callbacks structure that outlives the tree and/or parser.
6837//
6838// See also sample_static_trees() for an example on how to use
6839// trees with static lifetime.
6840
6841/** @addtogroup doc_quickstart_helpers
6842 * @{ */
6844{
6845 std::vector<char> memory_pool = std::vector<char>(10u * 1024u); // 10KB
6846 size_t num_allocs = 0, alloc_size = 0, corr_size = 0;
6848
6849 void *allocate(size_t len)
6850 {
6851 void *ptr = &memory_pool[alloc_size];
6852 alloc_size += len;
6853 ++num_allocs;
6854 // ensure the ptr is aligned
6855 uintptr_t uptr = (uintptr_t)ptr;
6856 const uintptr_t align = alignof(max_align_t);
6857 if (uptr % align)
6858 {
6859 uintptr_t prev = uptr - (uptr % align);
6860 uintptr_t next = prev + align;
6861 uintptr_t corr = next - uptr;
6862 ptr = (void*)(((char*)ptr) + corr);
6863 corr_size += corr;
6864 }
6865 C4_CHECK_MSG(alloc_size + corr_size <= memory_pool.size(),
6866 "out of memory! requested=%zu+%zu available=%zu\n",
6868 return ptr;
6869 }
6870
6871 void free(void *mem, size_t len)
6872 {
6873 CHECK((char*)mem >= &memory_pool.front() && (char*)mem < &memory_pool.back());
6874 CHECK((char*)mem+len >= &memory_pool.front() && (char*)mem+len <= &memory_pool.back());
6875 dealloc_size += len;
6876 ++num_deallocs;
6877 // no need to free here
6878 }
6879
6880 // bridge
6888 static void* s_allocate(size_t len, void* /*hint*/, void *this_)
6889 {
6890 return ((GlobalAllocatorExample*)this_)->allocate(len);
6891 }
6892 static void s_free(void *mem, size_t len, void *this_)
6893 {
6894 ((GlobalAllocatorExample*)this_)->free(mem, len);
6895 }
6896
6897 // checking
6903 {
6904 std::cout << "size: alloc=" << alloc_size << " dealloc=" << dealloc_size << std::endl;
6905 std::cout << "count: #allocs=" << num_allocs << " #deallocs=" << num_deallocs << std::endl;
6907 CHECK(alloc_size >= dealloc_size); // failure here means a double free
6908 CHECK(alloc_size == dealloc_size); // failure here means a leak
6909 num_allocs = 0;
6910 num_deallocs = 0;
6911 alloc_size = 0;
6912 dealloc_size = 0;
6913 }
6914};
6915/** @} */
6916
6917
6918/** demonstrates how to set the global allocator for ryml */
6920{
6922
6923 // save the existing callbacks for restoring
6925
6926 // set to our callbacks
6928
6929 // verify that the allocator is in effect
6930 ryml::Callbacks const& current = ryml::get_callbacks();
6931 CHECK(current.m_allocate == &mem.s_allocate);
6932 CHECK(current.m_free == &mem.s_free);
6933
6934 // so far nothing was allocated
6935 CHECK(mem.alloc_size == 0);
6936
6937 // parse one tree and check
6938 (void)ryml::parse_in_arena(R"({foo: bar})");
6939 mem.check_and_reset();
6940
6941 // parse another tree and check
6942 (void)ryml::parse_in_arena(R"([a, b, c, d, {foo: bar, money: pennys}])");
6943 mem.check_and_reset();
6944
6945 // verify that by reserving we save allocations
6946 {
6947 ryml::EventHandlerTree evt_handler;
6948 ryml::Parser parser(&evt_handler); // reuse a parser
6949 ryml::Tree tree; // reuse a tree
6950
6951 tree.reserve(10); // reserve the number of nodes
6952 tree.reserve_arena(100); // reserve the arena size
6953 parser.reserve_stack(10); // reserve the parser depth.
6954
6955 // since the parser stack uses Small Storage Optimization,
6956 // allocations will only happen with capacities higher than 16.
6957 CHECK(mem.num_allocs == 2); // tree, tree_arena and NOT the parser
6958
6959 parser.reserve_stack(20); // reserve the parser depth.
6960 CHECK(mem.num_allocs == 3); // tree, tree_arena and now the parser as well
6961
6962 // verify that no other allocations occur when parsing
6963 size_t size_before = mem.alloc_size;
6964 parse_in_arena(&parser, "", R"([a, b, c, d, {foo: bar, money: pennys}])", &tree);
6965 CHECK(mem.alloc_size == size_before);
6966 CHECK(mem.num_allocs == 3);
6967 }
6968 mem.check_and_reset();
6969
6970 // restore defaults.
6971 ryml::set_callbacks(defaults);
6972}
6973
6974
6975//-----------------------------------------------------------------------------
6976
6977/** @addtogroup doc_quickstart_helpers
6978 * @{ */
6979
6980/** an example for a per-tree memory allocator */
6982{
6983 std::vector<char> memory_pool = std::vector<char>(10u * 1024u); // 10KB
6984 size_t num_allocs = 0, alloc_size = 0;
6986
6988 {
6989 // Above we used static functions to bridge to our methods.
6990 // To show a different approach, we employ lambdas here.
6991 // Note that there can be no captures in the lambdas
6992 // because these are C-style function pointers.
6993 ryml::Callbacks cb;
6994 cb.m_user_data = (void*) this;
6995 cb.m_allocate = [](size_t len, void *, void *data){ return ((PerTreeMemoryExample*) data)->allocate(len); };
6996 cb.m_free = [](void *mem, size_t len, void *data){ return ((PerTreeMemoryExample*) data)->free(mem, len); };
6997 return cb;
6998 }
6999
7000 void *allocate(size_t len)
7001 {
7002 void *ptr = &memory_pool[alloc_size];
7003 alloc_size += len;
7004 ++num_allocs;
7005 if C4_UNLIKELY(alloc_size > memory_pool.size())
7006 {
7007 std::cerr << "out of memory! requested=" << alloc_size << " vs " << memory_pool.size() << " available" << std::endl; // LCOV_EXCL_LINE
7008 std::abort(); // LCOV_EXCL_LINE
7009 }
7010 return ptr;
7011 }
7012
7013 void free(void *mem, size_t len)
7014 {
7015 CHECK((char*)mem >= &memory_pool.front() && (char*)mem < &memory_pool.back());
7016 CHECK((char*)mem+len >= &memory_pool.front() && (char*)mem+len <= &memory_pool.back());
7017 dealloc_size += len;
7018 ++num_deallocs;
7019 // no need to free here
7020 }
7021
7022 // checking
7028 {
7029 std::cout << "size: alloc=" << alloc_size << " dealloc=" << dealloc_size << std::endl;
7030 std::cout << "count: #allocs=" << num_allocs << " #deallocs=" << num_deallocs << std::endl;
7032 CHECK(alloc_size >= dealloc_size); // failure here means a double free
7033 CHECK(alloc_size == dealloc_size); // failure here means a leak
7034 num_allocs = 0;
7035 num_deallocs = 0;
7036 alloc_size = 0;
7037 dealloc_size = 0;
7038 }
7039};
7040/** @} */
7041
7043{
7047
7048 // the trees will use the memory in the resources above,
7049 // with each tree using a separate resource
7050 {
7051 // Watchout: ensure that the lifetime of the callbacks target
7052 // exceeds the lifetime of the tree.
7053 ryml::EventHandlerTree evt_handler(mrp.callbacks());
7054 ryml::Parser parser(&evt_handler);
7055 ryml::Tree tree1(mr1.callbacks());
7056 ryml::Tree tree2(mr2.callbacks());
7057
7058 ryml::csubstr yml1 = "{a: b}";
7059 ryml::csubstr yml2 = "{c: d, e: f, g: [h, i, 0, 1, 2, 3]}";
7060
7061 parse_in_arena(&parser, "file1.yml", yml1, &tree1);
7062 parse_in_arena(&parser, "file2.yml", yml2, &tree2);
7063 }
7064
7065 CHECK(mrp.num_allocs == 0); // YAML depth not large enough to warrant a parser allocation
7066 CHECK(mr1.alloc_size <= mr2.alloc_size); // because yml2 has more nodes
7067}
7068
7069
7070//-----------------------------------------------------------------------------
7071
7072
7073/** shows how to work around the static initialization order fiasco
7074 * when using a static-duration ryml tree
7075 * @see https://en.cppreference.com/w/cpp/language/siof */
7077{
7078 // Static trees may incur a static initialization order
7079 // problem. This happens because a default-constructed tree will
7080 // obtain the callbacks from the current global setting, which may
7081 // not have been initialized due to undefined static
7082 // initialization order:
7083 //
7084 // ERROR! depends on ryml::get_callbacks() which may not have been initialized.
7085 //static ryml::Tree tree;
7086 //
7087 // To work around the issue, declare static callbacks
7088 // to explicitly initialize the static tree:
7089 static ryml::Callbacks callbacks = default_callbacks(); // use default callback members
7090 static ryml::Tree tree(callbacks); // OK
7091 // now you can use the tree as normal:
7092 ryml::parse_in_arena(R"(doe: "a deer, a female deer")", &tree);
7093 CHECK(tree["doe"].val() == "a deer, a female deer");
7094}
7095
7096
7097//-----------------------------------------------------------------------------
7098//-----------------------------------------------------------------------------
7099//-----------------------------------------------------------------------------
7100
7101
7102/** @addtogroup doc_quickstart_helpers
7103 * @{ */
7104
7105namespace /*anon*/ {
7106static int num_checks = 0;
7107static int num_failed_checks = 0;
7108static bool quiet_mode = false;
7109} // namespace /*anon*/
7110
7111void handle_args(int argc, const char* argv[])
7112{
7113 auto arg_matches = [](const char *arg, const char *shortform, const char *longform) {
7114 return (0 == strcmp(arg, shortform) || 0 == strcmp(arg, longform));
7115 };
7116 for(int i = 1; i < argc; ++i)
7117 if(arg_matches(argv[i], "-q", "--quiet"))
7118 quiet_mode = true;
7119}
7120
7121bool report_check(int line, const char *predicate, bool result)
7122{
7123 ++num_checks;
7124 const char *msg = predicate ? "OK! " : "OK!";
7125 if(!result)
7126 {
7127 ++num_failed_checks; // LCOV_EXCL_LINE
7128 msg = predicate ? "FAIL: " : "FAIL"; // LCOV_EXCL_LINE
7129 }
7130 if(!result || !quiet_mode)
7131 {
7132 std::cout << __FILE__ << ':' << line << ": " << msg << (predicate ? predicate : "") << std::endl;
7133 }
7134 return result;
7135}
7136
7137
7139{
7140 std::cout << "Completed " << num_checks << " checks." << std::endl;
7141 if(num_failed_checks)
7142 std::cout << "ERROR: " << num_failed_checks << '/' << num_checks << " checks failed." << std::endl; // LCOV_EXCL_LINE
7143 else
7144 std::cout << "SUCCESS!" << std::endl;
7145 return num_failed_checks;
7146}
7147
7148
7149//-----------------------------------------------------------------------------
7150// methods to provide default callbacks (needed when
7151// RYML_NO_DEFAULT_CALLBACKS is defined)
7152
7153namespace {
7154// LCOV_EXCL_START
7155/** dump (part of an) error message to terminal
7156 * @ingroup doc_quickstart_helpers */
7157void errdump(ryml::csubstr s)
7158{
7159 if(s.len)
7160 fwrite(s.str, 1, s.len, stderr); // NOLINT
7161}
7162/** finish printing an error message, and flush
7163 * @ingroup doc_quickstart_helpers */
7164void errend()
7165{
7166 fputc('\n', stderr); // NOLINT
7167 fflush(NULL); // NOLINT
7168}
7169// LCOV_EXCL_STOP
7170} // namespace
7171
7172
7173/** set up a bare-bones implementation of the callbacks
7174 * @ingroup doc_quickstart_helpers */
7176{
7177 return ryml::Callbacks{}
7178 .set_allocate([](size_t len, void* , void *){
7179 return malloc(len); // NOLINT
7180 })
7181 .set_free([](void* mem, size_t, void *){
7182 free(mem); // NOLINT
7183 })
7184 ///
7185 /// The default error callbacks won't be called in this
7186 /// quickstart, because no errors are expected. But we
7187 /// implement them here to show how a bare-bones implementation
7188 /// looks like, and also because they are needed when
7189 /// @ref RYML_NO_DEFAULT_CALLBACKS is defined.
7190 ///
7191 /// For a different (more involved) implementation of the error
7192 /// callbacks, see the implementation of @ref ErrorHandlerExample
7193 /// below.
7194 ///
7195 // LCOV_EXCL_START
7196 .set_error_basic([](ryml::csubstr msg, ryml::ErrorDataBasic const& errdata, void *){
7197 ryml::err_basic_format(errdump, msg, errdata); // format the message, printing to stderr
7198 errend(); // print newline and flush
7199 abort(); // abort (must never return: abort, or exception or setjmp)
7200 })
7201 .set_error_parse([](ryml::csubstr msg, ryml::ErrorDataParse const& errdata, void *){
7202 ryml::err_parse_format(errdump, msg, errdata); // format the message, printing to out
7203 errend(); // print newline and flush
7204 abort(); // abort (must never return: abort, or exception or setjmp)
7205 })
7206 .set_error_visit([](ryml::csubstr msg, ryml::ErrorDataVisit const& errdata, void *){
7207 ryml::err_visit_format(errdump, msg, errdata); // format the message, printing to out
7208 errend(); // print newline and flush
7209 abort(); // abort (must never return: abort, or exception or setjmp)
7210 });
7211 // LCOV_EXCL_STOP
7212}
7213
7214/** set up default callbacks when ryml does not provide them
7215 * (ie when @ref RYML_NO_DEFAULT_CALLBACKS is defined)
7216 * @ingroup doc_quickstart_helpers */
7218{
7219#ifdef RYML_NO_DEFAULT_CALLBACKS
7221#endif
7222}
7223
7224
7225//-----------------------------------------------------------------------------
7226// methods for the example error handler
7227
7228#ifndef C4_EXCEPTIONS
7229/*environment for setjmp*/
7230static std::jmp_buf s_jmp_env;
7231static std::string s_jmp_msg;
7232#endif
7233
7234/** checking that an assertion occurs while calling fn. assertions are
7235 * enabled if @ref RYML_USE_ASSERT is defined.
7236 * @ingroup doc_quickstart_helpers */
7237template<class Fn>
7239{
7240 #if RYML_USE_ASSERT
7241 return check_error_occurs(std::forward<Fn>(fn));
7242 #else
7243 (void)fn; // do nothing otherwise, as there would be undefined behavior
7244 return true;
7245 #endif
7246}
7247/** checking that an error occurs while calling fn
7248 * @ingroup doc_quickstart_helpers */
7249template<class Fn>
7251{
7252 RYML_SAVE_TEST_EXPFAIL_(); // a dev helper to dump all tests
7253 saved_msg_short.clear();
7254 saved_msg_full.clear();
7256 saved_basic_loc = {};
7257 saved_parse_loc = {};
7258 saved_visit_tree = {};
7260 bool got_error = false;
7261 #ifdef C4_EXCEPTIONS
7262 try
7263 {
7264 std::forward<Fn>(fn)();
7265 }
7266 catch(std::exception const&)
7267 {
7268 got_error = true;
7269 }
7270 #else
7271 if(setjmp(s_jmp_env) == 0)
7272 {
7273 std::forward<Fn>(fn)();
7274 }
7275 else
7276 {
7277 got_error = true;
7278 }
7279 #endif
7280 return got_error;
7281}
7282
7283namespace {
7284/** interrupt execution
7285 * @ingroup doc_quickstart_helpers */
7286[[noreturn]] void stopexec(std::string const& s)
7287{
7288 #ifdef C4_EXCEPTIONS
7289 throw std::runtime_error(s);
7290 #else
7291 s_jmp_msg = s;
7292 std::longjmp(s_jmp_env, 1); // jump to the corresponding call to setjmp().
7293 #endif
7294}
7295} // namespace
7296/** this is where the callback implementation goes. Remember that it must not return.
7297 * @ingroup doc_quickstart_helpers
7298 * */
7300{
7301 saved_msg_short.assign(msg.str, msg.len);
7302 // build a full error message with location
7304 saved_msg_full.append(s.str, s.len);
7305 }, msg, errdata);
7306 // Save the error params for subsequent testing in the quickstart.
7308 saved_basic_loc = errdata.location;
7309 stopexec(saved_msg_short);
7310}
7311/** this is where the callback implementation goes. Remember that it must not return.
7312 * @ingroup doc_quickstart_helpers
7313 * @see ryml::format_location_context
7314 * */
7316{
7317 saved_msg_short.assign(msg.str, msg.len);
7318 // build a full error message with location
7320 saved_msg_full.append(s.str, s.len);
7321 }, msg, errdata);
7322 // Save the error params for subsequent testing in the quickstart.
7323 //
7324 // To add the source context, the source buffer is required. If
7325 // the caller is interested in enriching the full message with the
7326 // source buffer context, he can ensure that the source buffer is
7327 // kept, and then arrange the handler to access it.
7328 // For now, we assign the full message without context:
7330 saved_basic_loc = errdata.cpploc;
7331 saved_parse_loc = errdata.ymlloc;
7332 stopexec(saved_msg_full);
7333}
7334/** this is where the callback implementation goes. Remember that it must not return.
7335 * @ingroup doc_quickstart_helpers
7336 * */
7338{
7339 saved_msg_short.assign(msg.str, msg.len);
7340 // build a full error message with location
7342 saved_msg_full.append(s.str, s.len);
7343 }, msg, errdata);
7344 // Save the error params for subsequent testing in the quickstart.
7345 //
7346 // To add the source context, the source buffer is required. If
7347 // the caller is interested in enriching the full message with the
7348 // source buffer context, he can ensure that the source buffer is
7349 // kept, and then arrange the handler to access it.
7350 // For now, we assign the full message without context:
7352 saved_basic_loc = errdata.cpploc;
7353 saved_visit_tree = errdata.tree;
7354 saved_visit_id = errdata.node;
7355 stopexec(saved_msg_full);
7356}
7357
7358/** trampoline function to call the object's method */
7359[[noreturn]] void ErrorHandlerExample::s_error_basic(ryml::csubstr msg, ryml::ErrorDataBasic const& errdata, void *this_)
7360{
7361 static_cast<ErrorHandlerExample*>(this_)->on_error_basic(msg, errdata);
7362}
7363/** trampoline function to call the object's method */
7364[[noreturn]] void ErrorHandlerExample::s_error_parse(ryml::csubstr msg, ryml::ErrorDataParse const& errdata, void *this_)
7365{
7366 static_cast<ErrorHandlerExample*>(this_)->on_error_parse(msg, errdata);
7367}
7368/** trampoline function to call the object's method */
7369[[noreturn]] void ErrorHandlerExample::s_error_visit(ryml::csubstr msg, ryml::ErrorDataVisit const& errdata, void *this_)
7370{
7371 static_cast<ErrorHandlerExample*>(this_)->on_error_visit(msg, errdata);
7372}
7373
7374
7375/** a helper to create the Callbacks object for the custom error handler
7376 * @ingroup doc_quickstart_helpers
7377 * */
7387
7388/** test that this handler is currently set */
7390{
7391 ryml::Callbacks const& current = ryml::get_callbacks();
7392 CHECK(current.m_error_basic == &s_error_basic);
7393 CHECK(current.m_error_parse == &s_error_parse);
7394 CHECK(current.m_error_visit == &s_error_visit);
7395 CHECK(current.m_allocate == original_callbacks.m_allocate);
7396 CHECK(current.m_free == original_callbacks.m_free);
7397}
7398/** test that this handler is currently not set */
7400{
7401 ryml::Callbacks const& current = ryml::get_callbacks();
7402 CHECK(current.m_error_basic != &s_error_basic);
7403 CHECK(current.m_error_parse != &s_error_parse);
7404 CHECK(current.m_error_visit != &s_error_visit);
7405 CHECK(current.m_allocate == original_callbacks.m_allocate);
7406 CHECK(current.m_free == original_callbacks.m_free);
7407}
7408
7409
7410/** @} */ // doc_quickstart_helpers
7411
7412
7413C4_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_user_container_types_brief()
serialize/deserialize container (map or seq) types: brief version
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
void write(ryml::NodeRef &n, Inner const &inner)
serializes as a seq
ryml::ReadResult read(ryml::ConstNodeRef const &n, Inner *inner)
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
@ NONE
an index to none
Definition common.hpp:131
@ 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
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_)
serializes as a map
Inner inner[2]
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