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