// ryml: quickstart /** @addtogroup doc_quickstart * * This file does a quick tour of ryml. It has multiple self-contained * and well-commented samples that illustrate how to use ryml, and how * it works. * * Although this is not a unit test, the samples are written as a * sequence of actions and predicate checks to better convey what is * the expected result at any stage. And to ensure the code here is * correct and up to date, it's also run as part of the CI tests. * * If something is unclear, please open an issue or send a pull * request at https://github.com/biojppm/rapidyaml . If you have an * issue while using ryml, it is also encouraged to try to reproduce * the issue here, or look first through the relevant section. * * Happy ryml'ing! * * ### Some guidance on building * * The directories that exist side-by-side with this file contain * several examples on how to build this with cmake, such that you can * hit the ground running. See [the relevant section of the main * README](https://github.com/biojppm/rapidyaml/tree/v0.10.0?tab=readme-ov-file#quickstart-samples) * for an overview of the different choices. I suggest starting first * with the `add_subdirectory` example, treating it just like any * other self-contained cmake project. * * Or very quickly, to build and run this sample on your PC, start by * creating this `CMakeLists.txt`: * ```cmake * cmake_minimum_required(VERSION 3.13) * project(ryml-quickstart LANGUAGES CXX) * include(FetchContent) * FetchContent_Declare(ryml * GIT_REPOSITORY https://github.com/biojppm/rapidyaml.git * GIT_TAG v0.10.0 * GIT_SHALLOW FALSE # ensure submodules are checked out * ) * FetchContent_MakeAvailable(ryml) * add_executable(ryml-quickstart ${ryml_SOURCE_DIR}/samples/quickstart.cpp) * target_link_libraries(ryml-quickstart ryml::ryml) * add_custom_target(run ryml-quickstart * COMMAND $ * DEPENDS ryml-quickstart * COMMENT "running: $") * ``` * Now run the following commands in the same folder: * ```bash * # configure the project * cmake -S . -B build * # build and run * cmake --build build --target ryml-quickstart -j * # optionally, open in your IDE * cmake --open build * ``` * * @{ */ /** @cond dev */ int report_checks(); /** @endcond */ /** @defgroup doc_sample_helpers Sample helpers * * Helper utilities used in the sample. */ //----------------------------------------------------------------------------- // ryml can be used as a single header, or as a simple library: #if defined(RYML_SINGLE_HEADER) // using the single header directly in the executable #define RYML_SINGLE_HDR_DEFINE_NOW #include #elif defined(RYML_SINGLE_HEADER_LIB) // using the single header from a library #include #else #include // is needed if interop with std containers is // desired; ryml itself does not use any STL container. // For this sample, we will be using std interop, so... #include // optional header, provided for std:: interop #include // needed for the examples below // optional header, definitions for error utilities to implement // error callbacks: #include #endif // these are needed for the examples below #include #include #include #include #ifdef C4_EXCEPTIONS #include #else #include #endif //----------------------------------------------------------------------------- /** @cond dev */ // CONTENTS: // // (Each function addresses a topic and is fully self-contained. Jump // to the function to find out about its topic.) void sample_lightning_overview(); ///< lightning overview of most common features void sample_quick_overview(); ///< quick overview of most common features void sample_substr(); ///< about ryml's string views (from c4core) void sample_parse_file(); ///< ready-to-go example of parsing a file from disk void sample_parse_in_place(); ///< parse a mutable YAML source buffer void sample_parse_in_arena(); ///< parse a read-only YAML source buffer void sample_parse_reuse_tree(); ///< parse into an existing tree, maybe into a node void sample_parse_reuse_parser(); ///< reuse an existing parser void sample_parse_reuse_tree_and_parser(); ///< how to reuse existing trees and parsers void sample_iterate_trees(); ///< visit individual nodes and iterate through trees void sample_create_trees(); ///< programatically create trees void sample_tree_arena(); ///< interact with the tree's serialization arena void sample_fundamental_types(); ///< serialize/deserialize fundamental types void sample_empty_null_values(); ///< serialize/deserialize/query empty or null values void sample_formatting(); ///< control formatting when serializing/deserializing void sample_base64(); ///< encode/decode base64 void sample_user_scalar_types(); ///< serialize/deserialize scalar (leaf/string) types void sample_user_container_types(); ///< serialize/deserialize container (map or seq) types void sample_std_types(); ///< serialize/deserialize STL containers void sample_float_precision(); ///< control precision of serialized floats void sample_emit_to_container(); ///< emit to memory, eg a string or vector-like container void sample_emit_to_stream(); ///< emit to a stream, eg std::ostream void sample_emit_to_file(); ///< emit to a FILE* void sample_emit_nested_node(); ///< pick a nested node as the root when emitting void sample_style(); ///< query/set node styles void sample_style_flow_ml_indent(); ///< control indentation of FLOW_ML containers void sample_style_flow_ml_filter(); ///< set the parser to pick FLOW_SL even if the container is multiline void sample_json(); ///< JSON parsing and emitting void sample_anchors_and_aliases(); ///< deal with YAML anchors and aliases void sample_anchors_and_aliases_create(); ///< how to create YAML anchors and aliases void sample_tags(); ///< deal with YAML type tags void sample_tag_directives(); ///< deal with YAML tag namespace directives void sample_docs(); ///< deal with YAML docs void sample_error_handler(); ///< set custom error handlers void sample_error_basic(); ///< handler for basic errors, and obtain a full error message with basic context void sample_error_parse(); ///< handler for parse errors, and obtain a full error message with parse context void sample_error_visit(); ///< handler for visit errors, and obtain a full error message with visit context void sample_error_visit_location(); ///< obtaining the YAML location from a visit error void sample_global_allocator(); ///< set a global allocator for ryml void sample_per_tree_allocator(); ///< set per-tree allocators void sample_static_trees(); ///< how to use static trees in ryml void sample_location_tracking(); ///< track node YAML source locations in the parsed tree int main() { sample_lightning_overview(); sample_quick_overview(); sample_substr(); sample_parse_file(); sample_parse_in_place(); sample_parse_in_arena(); sample_parse_reuse_tree(); sample_parse_reuse_parser(); sample_parse_reuse_tree_and_parser(); sample_iterate_trees(); sample_create_trees(); sample_tree_arena(); sample_fundamental_types(); sample_empty_null_values(); sample_formatting(); sample_base64(); sample_user_scalar_types(); sample_user_container_types(); sample_float_precision(); sample_std_types(); sample_emit_to_container(); sample_emit_to_stream(); sample_emit_to_file(); sample_emit_nested_node(); sample_style(); sample_style_flow_ml_indent(); sample_style_flow_ml_filter(); sample_json(); sample_anchors_and_aliases(); sample_tags(); sample_tag_directives(); sample_docs(); sample_error_handler(); sample_error_basic(); sample_error_parse(); sample_error_visit(); sample_error_visit_location(); sample_global_allocator(); sample_per_tree_allocator(); sample_static_trees(); sample_location_tracking(); return report_checks(); } /** @endcond */ //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- C4_SUPPRESS_WARNING_GCC_CLANG_PUSH C4_SUPPRESS_WARNING_GCC_CLANG("-Wcast-qual") C4_SUPPRESS_WARNING_GCC_CLANG("-Wold-style-cast") C4_SUPPRESS_WARNING_GCC("-Wuseless-cast") //----------------------------------------------------------------------------- // first, some helpers used in this quickstart /** @addtogroup doc_sample_helpers * * @{ */ /** an example error handler, required for some of the quickstart * examples. * @ingroup doc_sample_helpers */ struct ErrorHandlerExample { ErrorHandlerExample() : defaults(ryml::get_callbacks()) {} ryml::Callbacks defaults; public: // utilities used below template bool check_error_occurs(Fn &&fn); template bool check_assertion_occurs(Fn &&fn); void check_enabled() const; void check_disabled() const; ryml::Callbacks callbacks(); public: // these are the functions that we want to execute on error [[noreturn]] void on_error_basic(ryml::csubstr msg, ryml::ErrorDataBasic const& errdata); [[noreturn]] void on_error_parse(ryml::csubstr msg, ryml::ErrorDataParse const& errdata); [[noreturn]] void on_error_visit(ryml::csubstr msg, ryml::ErrorDataVisit const& errdata); public: // these are the functions that we set ryml to call [[noreturn]] static void s_error_basic(ryml::csubstr msg, ryml::ErrorDataBasic const& errdata, void *this_); [[noreturn]] static void s_error_parse(ryml::csubstr msg, ryml::ErrorDataParse const& errdata, void *this_); [[noreturn]] static void s_error_visit(ryml::csubstr msg, ryml::ErrorDataVisit const& errdata, void *this_); public: // the handlers save these fields to be able to check on them // after the error is triggered. They are only valid after an // error, and before the next error. std::string saved_msg_short; std::string saved_msg_full; std::string saved_msg_full_with_context; ryml::Location saved_basic_loc; ryml::Location saved_parse_loc; ryml::Tree const* saved_visit_tree; ryml::id_type saved_visit_id; }; /** Shows how to create a scoped error handler. * @ingroup doc_sample_helpers */ struct ScopedErrorHandlerExample : public ErrorHandlerExample { ScopedErrorHandlerExample() : ErrorHandlerExample() { ryml::set_callbacks(callbacks()); check_enabled(); } ~ScopedErrorHandlerExample() { ryml::set_callbacks(this->defaults); check_disabled(); } }; // helper functions for sample_parse_file() template CharContainer file_get_contents(const char *filename); template size_t file_get_contents(const char *filename, CharContainer *v); template void file_put_contents(const char *filename, CharContainer const& v, const char* access="wb"); void file_put_contents(const char *filename, const char *buf, size_t sz, const char* access); bool report_check(int line, const char *predicate, bool result); #if defined(__DOXYGEN__) || defined(_DOXYGEN_) /// a quick'n'dirty assertion to verify a predicate # define CHECK(predicate) assert(predicate) // enable doxygen to link to the functions called inside CHECK() #else # if !(defined(__GNUC__) && (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)) /// a quick'n'dirty assertion to verify a predicate # define CHECK(predicate) do { if(!report_check(__LINE__, #predicate, (predicate))) { RYML_DEBUG_BREAK(); } } while(0) # else // GCC 4.8 has a problem with the CHECK() macro /// a quick'n'dirty assertion to verify a predicate # define CHECK CheckPredicate{__FILE__, __LINE__} struct CheckPredicate { const char *file; const int line; void operator() (bool result) const { if (!report_check(line, nullptr, result)) { RYML_DEBUG_BREAK(); } } }; # endif // GCC 4.8 #endif // doxygen /** @} */ // doc_sample_helpers //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** a lightning tour over most features * see @ref sample_quick_overview */ void sample_lightning_overview() { // Parse YAML code in place, potentially mutating the buffer: char yml_buf[] = "{foo: 1, bar: [2, 3], john: doe}"; ryml::Tree tree = ryml::parse_in_place(yml_buf); // read from the tree: ryml::NodeRef bar = tree["bar"]; CHECK(bar[0].val() == "2"); CHECK(bar[1].val() == "3"); CHECK(bar[0].val().str == yml_buf + 15); // points at the source buffer CHECK(bar[1].val().str == yml_buf + 18); // deserializing: int bar0 = 0, bar1 = 0; bar[0] >> bar0; bar[1] >> bar1; CHECK(bar0 == 2); CHECK(bar1 == 3); // serializing: bar[0] << 10; // creates a string in the tree's arena bar[1] << 11; CHECK(bar[0].val() == "10"); CHECK(bar[1].val() == "11"); // add nodes bar.append_child() << 12; // see also operator= (explanation below) CHECK(bar[2].val() == "12"); // emit tree // to std::string CHECK(ryml::emitrs_yaml(tree) == R"({foo: 1,bar: [10,11,12],john: doe})"); std::cout << tree; // emit to ostream ryml::emit_yaml(tree, stdout); // emit to FILE* // emit node ryml::ConstNodeRef foo = tree["foo"]; // to std::string CHECK(ryml::emitrs_yaml(foo) == "foo: 1\n"); std::cout << foo; // emit node to ostream ryml::emit_yaml(foo, stdout); // emit node to FILE* } //----------------------------------------------------------------------------- /** a brief tour over most features */ void sample_quick_overview() { // Parse YAML code in place, potentially mutating the buffer: char yml_buf[] = R"( foo: 1 bar: [2, 3] john: doe)"; ryml::Tree tree = ryml::parse_in_place(yml_buf); // The resulting tree contains only views to the parsed string. If // the string was parsed in place, then the string must outlive // the tree! This works in this case because both `yml_buf` and `tree` // live on the same scope, so have the same lifetime. // It is also possible to: // // - parse a read-only buffer using parse_in_arena(). This // copies the YAML buffer to the tree's arena, and spares the // headache of the string's lifetime. // // - reuse an existing tree (advised) // // - reuse an existing parser (advised) // // - parse into an existing node deep in a tree // // Note: it will always be significantly faster to parse in place // and reuse tree+parser. // // Below you will find samples that show how to achieve reuse; but // please note that for brevity and clarity, many of the examples // here are parsing in the arena, and not reusing tree or parser. //------------------------------------------------------------------ // API overview // ryml has a two-level API: // // The lower level index API is based on the indices of nodes, // where the node's id is the node's position in the tree's data // array. This API is very efficient, but somewhat difficult to use: ryml::id_type root_id = tree.root_id(); ryml::id_type bar_id = tree.find_child(root_id, "bar"); // need to get the index right CHECK(tree.is_map(root_id)); // all of the index methods are in the tree CHECK(tree.is_seq(bar_id)); // ... and receive the subject index // The node API is a lightweight abstraction sitting on top of the // index API, but offering a much more convenient interaction: ryml::ConstNodeRef root = tree.rootref(); // a const node reference ryml::ConstNodeRef bar = tree["bar"]; CHECK(root.is_map()); CHECK(bar.is_seq()); // A node ref is a lightweight handle to the tree and associated id: CHECK(root.tree() == &tree); // a node ref points at its tree, WITHOUT refcount CHECK(root.id() == root_id); // a node ref's id is the index of the node CHECK(bar.id() == bar_id); // a node ref's id is the index of the node // The node API translates very cleanly to the index API, so most // of the code examples below are using the node API. // WARNING. A node ref holds a raw pointer to the tree. Care must // be taken to ensure the lifetimes match, so that a node will // never access the tree after the goes out of scope. //------------------------------------------------------------------ // To read the parsed tree // ConstNodeRef::operator[] does a lookup, is O(num_children[node]). CHECK(tree["foo"].is_keyval()); CHECK(tree["foo"].val() == "1"); // get the val of a node (must be leaf node, otherwise it is a container and has no val) CHECK(tree["foo"].key() == "foo"); // get the key of a node (must be child of a map, otherwise it has no key) CHECK(tree["bar"].is_seq()); CHECK(tree["bar"].has_key()); CHECK(tree["bar"].key() == "bar"); // maps use string keys, seqs use index keys: CHECK(tree["bar"][0].val() == "2"); CHECK(tree["bar"][1].val() == "3"); CHECK(tree["john"].val() == "doe"); // An index key is the position of the child within its parent, // so even maps can also use int keys, if the key position is // known. CHECK(tree[0].id() == tree["foo"].id()); CHECK(tree[1].id() == tree["bar"].id()); CHECK(tree[2].id() == tree["john"].id()); // Tree::operator[](int) searches a ***root*** child by its position. CHECK(tree[0].id() == tree["foo"].id()); // 0: first child of root CHECK(tree[1].id() == tree["bar"].id()); // 1: second child of root CHECK(tree[2].id() == tree["john"].id()); // 2: third child of root // NodeRef::operator[](int) searches a ***node*** child by its position: CHECK(bar[0].val() == "2"); // 0 means first child of bar CHECK(bar[1].val() == "3"); // 1 means second child of bar // NodeRef::operator[](string): // A string key is the key of the node: lookup is by name. So it // is only available for maps, and it is NOT available for seqs, // since seq members do not have keys. CHECK(tree["foo"].key() == "foo"); CHECK(tree["bar"].key() == "bar"); CHECK(tree["john"].key() == "john"); CHECK(bar.is_seq()); // CHECK(bar["BOOM!"].is_seed()); // error, seqs do not have key lookup // Note that maps can also use index keys as well as string keys: CHECK(root["foo"].id() == root[0].id()); CHECK(root["bar"].id() == root[1].id()); CHECK(root["john"].id() == root[2].id()); // IMPORTANT. The ryml tree uses an index-based linked list for // storing children, so the complexity of // `Tree::operator[csubstr]` and `Tree::operator[id_type]` is O(n), // linear on the number of root children. If you use // `Tree::operator[]` with a large tree where the root has many // children, you will see a performance hit. // // To avoid this hit, you can create your own accelerator // structure. For example, before doing a lookup, do a single // traverse at the root level to fill an `map` // mapping key names to node indices; with a node index, a lookup // (via `Tree::get()`) is O(1), so this way you can get O(log n) // lookup from a key. (But please do not use `std::map` if you // care about performance; use something else like a flat map or // sorted vector). // // As for node refs, the difference from `NodeRef::operator[]` and // `ConstNodeRef::operator[]` to `Tree::operator[]` is that the // latter refers to the root node, whereas the former are invoked // on their target node. But the lookup process works the same for // both and their algorithmic complexity is the same: they are // both linear in the number of direct children. But of course, // depending on the data, that number may be very different from // one to another. //------------------------------------------------------------------ // Hierarchy: { ryml::ConstNodeRef foo = root.first_child(); ryml::ConstNodeRef john = root.last_child(); CHECK(tree.size() == 6); // O(1) number of nodes in the tree CHECK(root.num_children() == 3); // O(num_children[root]) CHECK(foo.num_siblings() == 3); // O(num_children[parent(foo)]) CHECK(foo.parent().id() == root.id()); // parent() is O(1) CHECK(root.first_child().id() == root["foo"].id()); // first_child() is O(1) CHECK(root.last_child().id() == root["john"].id()); // last_child() is O(1) CHECK(john.first_sibling().id() == foo.id()); CHECK(foo.last_sibling().id() == john.id()); // prev_sibling(), next_sibling(): (both are O(1)) CHECK(foo.num_siblings() == root.num_children()); CHECK(foo.prev_sibling().id() == ryml::NONE); // foo is the first_child() CHECK(foo.next_sibling().key() == "bar"); CHECK(foo.next_sibling().next_sibling().key() == "john"); CHECK(foo.next_sibling().next_sibling().next_sibling().id() == ryml::NONE); // john is the last_child() } //------------------------------------------------------------------ // Iterating: { ryml::csubstr expected_keys[] = {"foo", "bar", "john"}; // iterate children using the high-level node API: { ryml::id_type count = 0; for(ryml::ConstNodeRef const& child : root.children()) CHECK(child.key() == expected_keys[count++]); } // iterate siblings using the high-level node API: { ryml::id_type count = 0; for(ryml::ConstNodeRef const& child : root["foo"].siblings()) CHECK(child.key() == expected_keys[count++]); } // iterate children using the lower-level tree index API: { ryml::id_type count = 0; for(ryml::id_type child_id = tree.first_child(root_id); child_id != ryml::NONE; child_id = tree.next_sibling(child_id)) CHECK(tree.key(child_id) == expected_keys[count++]); } // iterate siblings using the lower-level tree index API: // (notice the only difference from above is in the loop // preamble, which calls tree.first_sibling(bar_id) instead of // tree.first_child(root_id)) { ryml::id_type count = 0; for(ryml::id_type child_id = tree.first_sibling(bar_id); child_id != ryml::NONE; child_id = tree.next_sibling(child_id)) CHECK(tree.key(child_id) == expected_keys[count++]); } } //------------------------------------------------------------------ // Gotchas: // ryml uses assertions to prevent you from trying to obtain // things that do not exist. For example: { ryml::ConstNodeRef seq_node = tree["bar"]; ryml::ConstNodeRef val_node = seq_node[0]; CHECK(seq_node.is_seq()); // seq is a container CHECK(!seq_node.has_val()); // ... so it has no val //CHECK(seq_node.val() == BOOM!); // ... so attempting to get a val is undefined behavior CHECK(val_node.parent() == seq_node); // belongs to a seq CHECK(!val_node.has_key()); // ... so it has no key //CHECK(val_node.key() == BOOM!); // ... so attempting to get a key is undefined behavior CHECK(val_node.is_val()); // this node is a val //CHECK(val_node.first_child() == BOOM!); // ... so attempting to get a child is undefined behavior // assertions are also present in methods that /may/ read the val: CHECK(seq_node.is_seq()); // seq is a container //CHECK(seq_node.val_is_null() BOOM!); // so cannot get the val to check } // By default, assertions are enabled unless the NDEBUG macro is // defined (which happens in release builds). // // This adheres to the pay-only-for-what-you-use philosophy: if // you are sure that your intent is correct, why would you need to // pay the runtime cost for the assertions? // // The downside, of course, is that you may be unsure, or your // code may be wrong; and then release builds may end up doing // something wrong. // // So in that case, you can use the appropriate ryml predicates to // check your intent (as in the examples above), or you can // explicitly enable/disable assertions, by defining the macro // RYML_USE_ASSERT to a proper value (see c4/yml/common.hpp). This // will make problematic code trigger a call to the appropriate // error callback. // // Also, to be clear, this does not apply to parse errors // occurring when the YAML is parsed. Parse errors are always // detected by the parser: this is not done through assertions, // but through the appropriate error checking mechanism. So // checking for these errors is always enabled and cannot be // switched off. //------------------------------------------------------------------ // Deserializing: use operator>> { int foo = 0, bar0 = 0, bar1 = 0; std::string john_str; std::string bar_str; root["foo"] >> foo; root["bar"][0] >> bar0; root["bar"][1] >> bar1; root["john"] >> john_str; // requires from_chars(std::string). see serialization samples below. root["bar"] >> ryml::key(bar_str); // to deserialize the key, use the tag function ryml::key() CHECK(foo == 1); CHECK(bar0 == 2); CHECK(bar1 == 3); CHECK(john_str == "doe"); CHECK(bar_str == "bar"); } //------------------------------------------------------------------ // Modifying existing nodes: operator= vs operator<< // As implied by its name, ConstNodeRef is a reference to a const // node. It can be used to read from the node, but not write to it // or modify the hierarchy of the node. If any modification is // desired then a NodeRef must be used instead: ryml::NodeRef wroot = tree.rootref(); // writeable root // operator= assigns an existing string to the receiving node. // The contents are NOT copied, and the string pointer will be in // effect until the tree goes out of scope! So BEWARE to only // assign from strings outliving the tree. wroot["foo"] = "says you"; wroot["bar"][0] = "-2"; wroot["bar"][1] = "-3"; wroot["john"] = "ron"; // Now the tree is _pointing_ at the memory of the strings above. // In this case it is OK because those are static strings, located // in the executable's static section, and will outlive the tree. CHECK(root["foo"].val() == "says you"); CHECK(root["bar"][0].val() == "-2"); CHECK(root["bar"][1].val() == "-3"); CHECK(root["john"].val() == "ron"); // But WATCHOUT: do not assign from temporary objects: // { // std::string crash("will dangle"); // root["john"] = ryml::to_csubstr(crash); // } // CHECK(root["john"] == "dangling"); // CRASH! the string was deallocated // operator<<: for cases where the lifetime of the string is // problematic WRT the tree, you can create and save a string in // the tree using operator<<. It first serializes values to a // string arena owned by the tree, then assigns the serialized // string to the receiving node. This avoids constraints with the // lifetime, since the arena lives with the tree. CHECK(tree.arena().empty()); wroot["foo"] << "says who"; // requires to_chars(). see serialization samples below. wroot["bar"][0] << 20; wroot["bar"][1] << 30; wroot["john"] << "deere"; CHECK(root["foo"].val() == "says who"); CHECK(root["bar"][0].val() == "20"); CHECK(root["bar"][1].val() == "30"); CHECK(root["john"].val() == "deere"); CHECK(tree.arena() == "says who2030deere"); // the result of serializations to the tree arena // using operator<< instead of operator=, the crash above is avoided: { std::string ok("in_scope"); // root["john"] = ryml::to_csubstr(ok); // don't, will dangle wroot["john"] << ryml::to_csubstr(ok); // OK, copy to the tree's arena } CHECK(root["john"].val() == "in_scope"); // OK! val is now in the tree's arena // serializing floating points: wroot["float"] << 2.4f; // to force a particular precision or float format: // (see sample_float_precision() and sample_formatting()) wroot["digits"] << ryml::fmt::real(2.4, /*num_digits*/6, ryml::FTOA_FLOAT); CHECK(tree.arena() == "says who2030deerein_scope2.42.400000"); // the result of serializations to the tree arena //------------------------------------------------------------------ // Adding new nodes: // adding a keyval node to a map: CHECK(root.num_children() == 5); wroot["newkeyval"] = "shiny and new"; // using these strings wroot.append_child() << ryml::key("newkeyval (serialized)") << "shiny and new (serialized)"; // serializes and assigns the serialization CHECK(root.num_children() == 7); CHECK(root["newkeyval"].key() == "newkeyval"); CHECK(root["newkeyval"].val() == "shiny and new"); CHECK(root["newkeyval (serialized)"].key() == "newkeyval (serialized)"); CHECK(root["newkeyval (serialized)"].val() == "shiny and new (serialized)"); CHECK( ! tree.in_arena(root["newkeyval"].key())); // it's using directly the static string above CHECK( ! tree.in_arena(root["newkeyval"].val())); // it's using directly the static string above CHECK( tree.in_arena(root["newkeyval (serialized)"].key())); // it's using a serialization of the string above CHECK( tree.in_arena(root["newkeyval (serialized)"].val())); // it's using a serialization of the string above // adding a val node to a seq: CHECK(root["bar"].num_children() == 2); wroot["bar"][2] = "oh so nice"; wroot["bar"][3] << "oh so nice (serialized)"; CHECK(root["bar"].num_children() == 4); CHECK(root["bar"][2].val() == "oh so nice"); CHECK(root["bar"][3].val() == "oh so nice (serialized)"); // adding a seq node: CHECK(root.num_children() == 7); wroot["newseq"] |= ryml::SEQ; wroot.append_child() << ryml::key("newseq (serialized)") |= ryml::SEQ; CHECK(root.num_children() == 9); CHECK(root["newseq"].num_children() == 0); CHECK(root["newseq"].is_seq()); CHECK(root["newseq (serialized)"].num_children() == 0); CHECK(root["newseq (serialized)"].is_seq()); // adding a map node: CHECK(root.num_children() == 9); wroot["newmap"] |= ryml::MAP; wroot.append_child() << ryml::key("newmap (serialized)") |= ryml::MAP; CHECK(root.num_children() == 11); CHECK(root["newmap"].num_children() == 0); CHECK(root["newmap"].is_map()); CHECK(root["newmap (serialized)"].num_children() == 0); CHECK(root["newmap (serialized)"].is_map()); // // When the tree is mutable, operator[] first searches the tree // for the does not mutate the tree until the returned node is // written to. // // Until such time, the NodeRef object keeps in itself the required // information to write to the proper place in the tree. This is // called being in a "seed" state. // // This means that passing a key/index which does not exist will // not mutate the tree, but will instead store (in the node) the // proper place of the tree to be able to do so, if and when it is // required. This is why the node is said to be in "seed" state - // it allows creating the entry in the tree in the future. // // This is a significant difference from eg, the behavior of // std::map, which mutates the map immediately within the call to // operator[]. // // All of the points above apply only if the tree is mutable. If // the tree is const, then a NodeRef cannot be obtained from it; // only a ConstNodeRef, which can never be used to mutate the // tree. // CHECK(!root.has_child("I am not nothing")); ryml::NodeRef nothing; CHECK(nothing.invalid()); // invalid because it points at nothing nothing = wroot["I am nothing"]; CHECK(!nothing.invalid()); // points at the tree, and a specific place in the tree CHECK(nothing.is_seed()); // ... but nothing is there yet. CHECK(!root.has_child("I am nothing")); // same as above CHECK(!nothing.readable()); // ... and this node cannot be used to // read anything from the tree ryml::NodeRef something = wroot["I am something"]; ryml::ConstNodeRef constsomething = wroot["I am something"]; CHECK(!root.has_child("I am something")); // same as above CHECK(!something.invalid()); CHECK(something.is_seed()); // same as above CHECK(!something.readable()); // same as above CHECK(constsomething.invalid()); // NOTE: because a ConstNodeRef cannot be // used to mutate a tree, it is only valid() // if it is pointing at an existing node. something = "indeed"; // this will commit the seed to the tree, mutating at the proper place CHECK(root.has_child("I am something")); CHECK(root["I am something"].val() == "indeed"); CHECK(!something.invalid()); // it was already valid CHECK(!something.is_seed()); // now the tree has this node, so the // ref is no longer a seed CHECK(something.readable()); // and it is now readable // // now the constref is also valid (but it needs to be reassigned): ryml::ConstNodeRef constsomethingnew = wroot["I am something"]; CHECK(!constsomethingnew.invalid()); CHECK(constsomethingnew.readable()); // note that the old constref is now stale, because it only keeps // the state at creation: CHECK(constsomething.invalid()); CHECK(!constsomething.readable()); // // ----------------------------------------------------------- // Remember: a seed node cannot be used to read from the tree! // ----------------------------------------------------------- // // The seed node needs to be created and become readable first. // // Trying to invoke any tree-reading method on a node that is not // readable will cause an assertion (see RYML_USE_ASSERT). // // It is your responsibility to verify that the preconditions are // met. If you are not sure about the structure of your data, // write your code defensively to signify your full intent: // ryml::NodeRef wbar = wroot["bar"]; if(wbar.readable() && wbar.is_seq()) // .is_seq() requires .readable() { CHECK(wbar[0].readable() && wbar[0].val() == "20"); CHECK( ! wbar[100].readable()); CHECK( ! wbar[100].readable() || wbar[100].val() == "100"); // <- no crash because it is not .readable(), so never tries to call .val() // this would work as well: CHECK( ! wbar[0].is_seed() && wbar[0].val() == "20"); CHECK(wbar[100].is_seed() || wbar[100].val() == "100"); } //------------------------------------------------------------------ // .operator[]() vs .at() // (Const)NodeRef::operator[] is an analogue to std::vector::operator[]. // (Const)NodeRef::at() is an analogue to std::vector::at() // // at() will always check the subject node is .readable(). // // [] is meant for the happy path, and unverified in Release // builds. { // in this example we will be checking errors, so set up a // temporary error handler to catch them: ScopedErrorHandlerExample errh; // calls ryml::set_callbacks() // instantiate the tree after errh ryml::Tree err_tree = ryml::parse_in_arena("{foo: bar}"); // ... so that the tree uses the current callbacks: CHECK(err_tree.callbacks() == errh.callbacks()); // node does not exist, only a seed node ryml::NodeRef seed_node = err_tree["this"]; // ... therefore not .readable() CHECK(!seed_node.readable()); // using .at() reliably produces an error: CHECK(errh.check_error_occurs([&]{ return seed_node.at("is").at("an").at("invalid").at("operation"); // ^ // error occurs here because it is unreadable })); // ... but using [] fails only when RYML_USE_ASSERT is // defined. otherwise, it's the dreaded Undefined Behavior: CHECK(errh.check_assertion_occurs([&]{ return seed_node["is"]["an"]["invalid"]["operation"]; // ^ // assertion occurs here because it is unreadable })); } //------------------------------------------------------------------ // Emitting: ryml::csubstr expected_result = R"(foo: says who bar: [20,30,oh so nice,oh so nice (serialized)] john: in_scope float: 2.4 digits: 2.400000 newkeyval: shiny and new newkeyval (serialized): shiny and new (serialized) newseq: [] newseq (serialized): [] newmap: {} newmap (serialized): {} I am something: indeed )"; // emit to a FILE* ryml::emit_yaml(tree, stdout); // emit to a stream std::stringstream ss; ss << tree; std::string stream_result = ss.str(); // emit to a buffer: std::string str_result = ryml::emitrs_yaml(tree); // can emit to any given buffer: char buf[1024]; ryml::csubstr buf_result = ryml::emit_yaml(tree, buf); // now check CHECK(buf_result == expected_result); CHECK(str_result == expected_result); CHECK(stream_result == expected_result); // There are many possibilities to emit to buffer; // please look at the emit sample functions below. //------------------------------------------------------------------ // ConstNodeRef vs NodeRef ryml::NodeRef noderef = tree["bar"][0]; ryml::ConstNodeRef constnoderef = tree["bar"][0]; // ConstNodeRef cannot be used to mutate the tree: //constnoderef = "21"; // compile error //constnoderef << "22"; // compile error // ... but a NodeRef can: noderef = "21"; // ok, can assign because it's not const CHECK(tree["bar"][0].val() == "21"); noderef << "22"; // ok, can serialize and assign because it's not const CHECK(tree["bar"][0].val() == "22"); // it is not possible to obtain a NodeRef from a ConstNodeRef: // noderef = constnoderef; // compile error // it is always possible to obtain a ConstNodeRef from a NodeRef: constnoderef = noderef; // ok can assign const <- nonconst // If a tree is const, then only ConstNodeRef's can be // obtained from that tree: ryml::Tree const& consttree = tree; //noderef = consttree["bar"][0]; // compile error noderef = tree["bar"][0]; // ok constnoderef = consttree["bar"][0]; // ok // ConstNodeRef and NodeRef can be compared for equality. // Equality means they point at the same node. CHECK(constnoderef == noderef); CHECK(!(constnoderef != noderef)); //------------------------------------------------------------------ // Getting the location of nodes in the source: // // Location tracking is opt-in: ryml::EventHandlerTree evt_handler = {}; ryml::Parser parser(&evt_handler, ryml::ParserOptions().locations(true)); // Now the parser will start by building the accelerator structure: ryml::Tree tree2 = parse_in_arena(&parser, "expected.yml", expected_result); // ... and use it when querying ryml::ConstNodeRef subject_node = tree2["bar"][1]; CHECK(subject_node.val() == "30"); ryml::Location loc = subject_node.location(parser); CHECK(parser.location_contents(loc).begins_with("30")); CHECK(loc.line == 1u); CHECK(loc.col == 9u); // For further details in location tracking, // refer to the sample function below. //------------------------------------------------------------------ // Dealing with UTF8 ryml::Tree langs = ryml::parse_in_arena(R"( en: Planet (Gas) fr: Planète (Gazeuse) ru: Планета (Газ) ja: 惑星(ガス) zh: 行星(气体) # UTF8 decoding only happens in double-quoted strings, # as per the YAML standard decode this: "\u263A c\x61f\xE9" and this as well: "\u2705 \U0001D11E" not decoded: '\u263A \xE2\x98\xBA' neither this: '\u2705 \U0001D11E' )"); // in-place UTF8 just works: CHECK(langs["en"].val() == "Planet (Gas)"); CHECK(langs["fr"].val() == "Planète (Gazeuse)"); CHECK(langs["ru"].val() == "Планета (Газ)"); CHECK(langs["ja"].val() == "惑星(ガス)"); CHECK(langs["zh"].val() == "行星(气体)"); // and \x \u \U codepoints are decoded, but only when they appear // inside double-quoted strings, as dictated by the YAML // standard: CHECK(langs["decode this"].val() == "☺ café"); CHECK(langs["and this as well"].val() == "✅ 𝄞"); CHECK(langs["not decoded"].val() == "\\u263A \\xE2\\x98\\xBA"); CHECK(langs["neither this"].val() == "\\u2705 \\U0001D11E"); } //----------------------------------------------------------------------------- /** demonstrate usage of ryml::substr and ryml::csubstr * * These types are imported from the c4core library into the ryml * namespace You may have noticed above the use of a `csubstr` * class. This class is defined in another library, * [c4core](https://github.com/biojppm/c4core), which is imported by * ryml. This is a library I use with my projects consisting of * multiplatform low-level utilities. One of these is `c4::csubstr` * (the name comes from "constant substring") which is a non-owning * read-only string view, with many methods that make it practical to * use (I would certainly argue more practical than `std::string`). In * fact, `c4::csubstr` and its writeable counterpart `c4::substr` are * the workhorses of the ryml parsing and serialization code. * * @see doc_substr */ void sample_substr() { // substr is a mutable view: pointer and length to a string in memory. // csubstr is a const-substr (immutable). // construct from explicit args { const char foobar_str[] = "foobar"; auto s = ryml::csubstr(foobar_str, strlen(foobar_str)); CHECK(s == "foobar"); CHECK(s.size() == 6); CHECK(s.data() == foobar_str); CHECK(s.size() == s.len); CHECK(s.data() == s.str); } // construct from a string array { const char foobar_str[] = "foobar"; ryml::csubstr s = foobar_str; CHECK(s == "foobar"); CHECK(s != "foobar0"); CHECK(s.size() == 6); // does not include the terminating \0 CHECK(s.data() == foobar_str); CHECK(s.size() == s.len); CHECK(s.data() == s.str); } // you can also declare directly in-place from an array: { ryml::csubstr s = "foobar"; CHECK(s == "foobar"); CHECK(s != "foobar0"); CHECK(s.size() == 6); CHECK(s.size() == s.len); CHECK(s.data() == s.str); } // construct from a C-string: // // Since the input is only a pointer, the string length can only // be found with a call to strlen(). To make this cost evident, we // require calling to_csubstr(): { const char *foobar_str = "foobar"; ryml::csubstr s = ryml::to_csubstr(foobar_str); CHECK(s == "foobar"); CHECK(s != "foobar0"); CHECK(s.size() == 6); CHECK(s.size() == s.len); CHECK(s.data() == s.str); } // construct from a std::string: same approach as above. // requires inclusion of the header // or of the umbrella header . // // not including in the default header is a deliberate // design choice to avoid including the heavy std:: allocation // machinery { std::string foobar_str = "foobar"; ryml::csubstr s = ryml::to_csubstr(foobar_str); // defined in CHECK(s == "foobar"); CHECK(s != "foobar0"); CHECK(s.size() == 6); CHECK(s.size() == s.len); CHECK(s.data() == s.str); } // convert substr -> csubstr { char buf[] = "foo"; ryml::substr foo = buf; CHECK(foo.len == 3); CHECK(foo.data() == buf); ryml::csubstr cfoo = foo; CHECK(cfoo.data() == buf); } // cannot convert csubstr -> substr: { // ryml::substr foo2 = cfoo; // compile error: cannot write to csubstr } // construct from char[]/const char[]: mutable vs immutable memory { char const foobar_str_ro[] = "foobar"; // ro := read-only char foobar_str_rw[] = "foobar"; // rw := read-write static_assert(std::is_array::value, "this is an array"); static_assert(std::is_array::value, "this is an array"); // csubstr <- read-only memory { ryml::csubstr foobar = foobar_str_ro; CHECK(foobar.data() == foobar_str_ro); CHECK(foobar.size() == strlen(foobar_str_ro)); CHECK(foobar == "foobar"); // AKA strcmp } // csubstr <- read-write memory: you can create an immutable csubstr from mutable memory { ryml::csubstr foobar = foobar_str_rw; CHECK(foobar.data() == foobar_str_rw); CHECK(foobar.size() == strlen(foobar_str_rw)); CHECK(foobar == "foobar"); // AKA strcmp } // substr <- read-write memory. { ryml::substr foobar = foobar_str_rw; CHECK(foobar.data() == foobar_str_rw); CHECK(foobar.size() == strlen(foobar_str_rw)); CHECK(foobar == "foobar"); // AKA strcmp } // substr <- ro is impossible. { //ryml::substr foobar = foobar_str_ro; // compile error! } } // construct from char*/const char*: mutable vs immutable memory. // use to_substr()/to_csubstr() { char const* foobar_str_ro = "foobar"; // ro := read-only char foobar_str_rw_[] = "foobar"; // rw := read-write char * foobar_str_rw = foobar_str_rw_; // rw := read-write static_assert(!std::is_array::value, "this is a decayed pointer"); static_assert(!std::is_array::value, "this is a decayed pointer"); // csubstr <- read-only memory { //ryml::csubstr foobar = foobar_str_ro; // compile error: length is not known ryml::csubstr foobar = ryml::to_csubstr(foobar_str_ro); CHECK(foobar.data() == foobar_str_ro); CHECK(foobar.size() == strlen(foobar_str_ro)); CHECK(foobar == "foobar"); // AKA strcmp } // csubstr <- read-write memory: you can create an immutable csubstr from mutable memory { ryml::csubstr foobar = ryml::to_csubstr(foobar_str_rw); CHECK(foobar.data() == foobar_str_rw); CHECK(foobar.size() == strlen(foobar_str_rw)); CHECK(foobar == "foobar"); // AKA strcmp } // substr <- read-write memory. { ryml::substr foobar = ryml::to_substr(foobar_str_rw); CHECK(foobar.data() == foobar_str_rw); CHECK(foobar.size() == strlen(foobar_str_rw)); CHECK(foobar == "foobar"); // AKA strcmp } // substr <- read-only is impossible. { //ryml::substr foobar = ryml::to_substr(foobar_str_ro); // compile error! } } // substr is mutable, without changing the size: { char buf[] = "foobar"; ryml::substr foobar = buf; CHECK(foobar == "foobar"); foobar[0] = 'F'; CHECK(foobar == "Foobar"); foobar.back() = 'R'; CHECK(foobar == "FoobaR"); foobar.reverse(); CHECK(foobar == "RabooF"); foobar.reverse(); CHECK(foobar == "FoobaR"); foobar.reverse_sub(1, 4); CHECK(foobar == "FabooR"); foobar.reverse_sub(1, 4); CHECK(foobar == "FoobaR"); foobar.reverse_range(2, 5); CHECK(foobar == "FoaboR"); foobar.reverse_range(2, 5); CHECK(foobar == "FoobaR"); foobar.replace('o', '0'); CHECK(foobar == "F00baR"); foobar.replace('a', '_'); CHECK(foobar == "F00b_R"); foobar.replace("_0b", 'a'); CHECK(foobar == "FaaaaR"); foobar.toupper(); CHECK(foobar == "FAAAAR"); foobar.tolower(); CHECK(foobar == "faaaar"); foobar.fill('.'); CHECK(foobar == "......"); // see also: // - .erase() // - .replace_all() } // sub-views { ryml::csubstr s = "fooFOObarBAR"; CHECK(s.len == 12u); // sub(): <- first,[num] CHECK(s.sub(0) == "fooFOObarBAR"); CHECK(s.sub(0, 12) == "fooFOObarBAR"); CHECK(s.sub(0, 3) == "foo" ); CHECK(s.sub(3) == "FOObarBAR"); CHECK(s.sub(3, 3) == "FOO" ); CHECK(s.sub(6) == "barBAR"); CHECK(s.sub(6, 3) == "bar" ); CHECK(s.sub(9) == "BAR"); CHECK(s.sub(9, 3) == "BAR"); // first(): <- length CHECK(s.first(0) == "" ); CHECK(s.first(1) == "f" ); CHECK(s.first(2) != "f" ); CHECK(s.first(2) == "fo" ); CHECK(s.first(3) == "foo"); // last(): <- length CHECK(s.last(0) == ""); CHECK(s.last(1) == "R"); CHECK(s.last(2) == "AR"); CHECK(s.last(3) == "BAR"); // range(): <- first, last CHECK(s.range(0, 12) == "fooFOObarBAR"); CHECK(s.range(1, 12) == "ooFOObarBAR"); CHECK(s.range(1, 11) == "ooFOObarBA" ); CHECK(s.range(2, 10) == "oFOObarB" ); CHECK(s.range(3, 9) == "FOObar" ); // offs(): offset from beginning, end CHECK(s.offs(0, 0) == "fooFOObarBAR"); CHECK(s.offs(1, 0) == "ooFOObarBAR"); CHECK(s.offs(1, 1) == "ooFOObarBA" ); CHECK(s.offs(2, 1) == "oFOObarBA" ); CHECK(s.offs(2, 2) == "oFOObarB" ); CHECK(s.offs(3, 3) == "FOObar" ); // right_of(): <- pos, include_pos CHECK(s.right_of(0, true) == "fooFOObarBAR"); CHECK(s.right_of(0, false) == "ooFOObarBAR"); CHECK(s.right_of(1, true) == "ooFOObarBAR"); CHECK(s.right_of(1, false) == "oFOObarBAR"); CHECK(s.right_of(2, true) == "oFOObarBAR"); CHECK(s.right_of(2, false) == "FOObarBAR"); CHECK(s.right_of(3, true) == "FOObarBAR"); CHECK(s.right_of(3, false) == "OObarBAR"); // left_of() <- pos, include_pos CHECK(s.left_of(12, false) == "fooFOObarBAR"); CHECK(s.left_of(11, true) == "fooFOObarBAR"); CHECK(s.left_of(11, false) == "fooFOObarBA" ); CHECK(s.left_of(10, true) == "fooFOObarBA" ); CHECK(s.left_of(10, false) == "fooFOObarB" ); CHECK(s.left_of( 9, true) == "fooFOObarB" ); CHECK(s.left_of( 9, false) == "fooFOObar" ); // left_of(),right_of() <- substr ryml::csubstr FOO = s.sub(3, 3); CHECK(s.is_super(FOO)); // required for the following CHECK(s.left_of(FOO) == "foo"); CHECK(s.right_of(FOO) == "barBAR"); } // printing a substr/csubstr using printf-like { ryml::csubstr s = "some substring"; ryml::csubstr some = s.first(4); CHECK(some == "some"); CHECK(s == "some substring"); // To print a csubstr using printf(), use the %.*s format specifier: { char result[32] = {0}; std::snprintf(result, sizeof(result), "%.*s", (int)some.len, some.str); printf("~~~%s~~~\n", result); CHECK(ryml::to_csubstr((const char*)result) == "some"); CHECK(ryml::to_csubstr((const char*)result) == some); } // But NOTE: because this is a string view type, in general // the C-string is NOT zero terminated. So NEVER print it // directly, or it will overflow past the end of the given // substr, with a potential unbounded access. For example, // this is bad: { char result[32] = {0}; std::snprintf(result, sizeof(result), "%s", some.str); // ERROR! do not print the c-string directly CHECK(ryml::to_csubstr((const char*)result) == "some substring"); CHECK(ryml::to_csubstr((const char*)result) == s); } } // printing a substr/csubstr using ostreams { ryml::csubstr s = "some substring"; ryml::csubstr some = s.first(4); CHECK(some == "some"); CHECK(s == "some substring"); // simple! just use plain operator<< { std::stringstream ss; ss << s; CHECK(ss.str() == "some substring"); // as expected CHECK(ss.str() == s); // as expected } // But NOTE: because this is a string view type, in general // the C-string is NOT zero terminated. So NEVER print it // directly, or it will overflow past the end of the given // substr, with a potential unbounded access. For example, // this is bad: { std::stringstream ss; ss << some.str; // ERROR! do not print the c-string directly CHECK(ss.str() == "some substring"); // NOT "some" CHECK(ss.str() == s); // NOT some } // this is also bad (the same) { std::stringstream ss; ss << some.data(); // ERROR! do not print the c-string directly CHECK(ss.str() == "some substring"); // NOT "some" CHECK(ss.str() == s); // NOT some } // this is ok: { std::stringstream ss; ss << some; CHECK(ss.str() == "some"); // ok CHECK(ss.str() == some); // ok } } // is_sub(),is_super() { ryml::csubstr foobar = "foobar"; ryml::csubstr foo = foobar.first(3); CHECK(foo.is_sub(foobar)); CHECK(foo.is_sub(foo)); CHECK(!foo.is_super(foobar)); CHECK(!foobar.is_sub(foo)); // identity comparison is true: CHECK(foo.is_super(foo)); CHECK(foo.is_sub(foo)); CHECK(foobar.is_sub(foobar)); CHECK(foobar.is_super(foobar)); } // overlaps() { ryml::csubstr foobar = "foobar"; ryml::csubstr foo = foobar.first(3); ryml::csubstr oba = foobar.offs(2, 1); ryml::csubstr abc = "abc"; CHECK(foobar.overlaps(foo)); CHECK(foobar.overlaps(oba)); CHECK(foo.overlaps(foobar)); CHECK(foo.overlaps(oba)); CHECK(!foo.overlaps(abc)); CHECK(!abc.overlaps(foo)); } // triml(): trim characters from the left // trimr(): trim characters from the right // trim(): trim characters from left AND right { CHECK(ryml::csubstr(" \t\n\rcontents without whitespace\t \n\r").trim("\t \n\r") == "contents without whitespace"); ryml::csubstr aaabbb = "aaabbb"; ryml::csubstr aaa___bbb = "aaa___bbb"; // trim a character: CHECK(aaabbb.triml('a') == aaabbb.last(3)); // bbb CHECK(aaabbb.trimr('a') == aaabbb); CHECK(aaabbb.trim ('a') == aaabbb.last(3)); // bbb CHECK(aaabbb.triml('b') == aaabbb); CHECK(aaabbb.trimr('b') == aaabbb.first(3)); // aaa CHECK(aaabbb.trim ('b') == aaabbb.first(3)); // aaa CHECK(aaabbb.triml('c') == aaabbb); CHECK(aaabbb.trimr('c') == aaabbb); CHECK(aaabbb.trim ('c') == aaabbb); CHECK(aaa___bbb.triml('a') == aaa___bbb.last(6)); // ___bbb CHECK(aaa___bbb.trimr('a') == aaa___bbb); CHECK(aaa___bbb.trim ('a') == aaa___bbb.last(6)); // ___bbb CHECK(aaa___bbb.triml('b') == aaa___bbb); CHECK(aaa___bbb.trimr('b') == aaa___bbb.first(6)); // aaa___ CHECK(aaa___bbb.trim ('b') == aaa___bbb.first(6)); // aaa___ CHECK(aaa___bbb.triml('c') == aaa___bbb); CHECK(aaa___bbb.trimr('c') == aaa___bbb); CHECK(aaa___bbb.trim ('c') == aaa___bbb); // trim ANY of the characters: CHECK(aaabbb.triml("ab") == ""); CHECK(aaabbb.trimr("ab") == ""); CHECK(aaabbb.trim ("ab") == ""); CHECK(aaabbb.triml("ba") == ""); CHECK(aaabbb.trimr("ba") == ""); CHECK(aaabbb.trim ("ba") == ""); CHECK(aaabbb.triml("cd") == aaabbb); CHECK(aaabbb.trimr("cd") == aaabbb); CHECK(aaabbb.trim ("cd") == aaabbb); CHECK(aaa___bbb.triml("ab") == aaa___bbb.last(6)); // ___bbb CHECK(aaa___bbb.triml("ba") == aaa___bbb.last(6)); // ___bbb CHECK(aaa___bbb.triml("cd") == aaa___bbb); CHECK(aaa___bbb.trimr("ab") == aaa___bbb.first(6)); // aaa___ CHECK(aaa___bbb.trimr("ba") == aaa___bbb.first(6)); // aaa___ CHECK(aaa___bbb.trimr("cd") == aaa___bbb); CHECK(aaa___bbb.trim ("ab") == aaa___bbb.range(3, 6)); // ___ CHECK(aaa___bbb.trim ("ba") == aaa___bbb.range(3, 6)); // ___ CHECK(aaa___bbb.trim ("cd") == aaa___bbb); } // unquoted(): { CHECK(ryml::csubstr(R"('this is is single quoted')").unquoted() == "this is is single quoted"); CHECK(ryml::csubstr(R"("this is is double quoted")").unquoted() == "this is is double quoted"); } // stripl(): remove pattern from the left // stripr(): remove pattern from the right { ryml::csubstr abc___cba = "abc___cba"; ryml::csubstr abc___abc = "abc___abc"; CHECK(abc___cba.stripl("abc") == abc___cba.last(6)); // ___cba CHECK(abc___cba.stripr("abc") == abc___cba); CHECK(abc___cba.stripl("ab") == abc___cba.last(7)); // c___cba CHECK(abc___cba.stripr("ab") == abc___cba); CHECK(abc___cba.stripl("a") == abc___cba.last(8)); // bc___cba, same as triml('a') CHECK(abc___cba.stripr("a") == abc___cba.first(8)); CHECK(abc___abc.stripl("abc") == abc___abc.last(6)); // ___abc CHECK(abc___abc.stripr("abc") == abc___abc.first(6)); // abc___ CHECK(abc___abc.stripl("ab") == abc___abc.last(7)); // c___cba CHECK(abc___abc.stripr("ab") == abc___abc); CHECK(abc___abc.stripl("a") == abc___abc.last(8)); // bc___cba, same as triml('a') CHECK(abc___abc.stripr("a") == abc___abc); } // begins_with()/ends_with() // begins_with_any()/ends_with_any() { ryml::csubstr s = "foobar123"; // char overloads CHECK(s.begins_with('f')); CHECK(s.ends_with('3')); CHECK(!s.ends_with('2')); CHECK(!s.ends_with('o')); // char[] overloads CHECK(s.begins_with("foobar")); CHECK(s.begins_with("foo")); CHECK(s.begins_with_any("foo")); CHECK(!s.begins_with("oof")); CHECK(s.begins_with_any("oof")); CHECK(s.ends_with("23")); CHECK(s.ends_with("123")); CHECK(s.ends_with_any("123")); CHECK(!s.ends_with("321")); CHECK(s.ends_with_any("231")); } // select() { ryml::csubstr s = "0123456789"; CHECK(s.select('0') == s.sub(0, 1)); CHECK(s.select('1') == s.sub(1, 1)); CHECK(s.select('2') == s.sub(2, 1)); CHECK(s.select('8') == s.sub(8, 1)); CHECK(s.select('9') == s.sub(9, 1)); CHECK(s.select("0123") == s.range(0, 4)); CHECK(s.select("012" ) == s.range(0, 3)); CHECK(s.select("01" ) == s.range(0, 2)); CHECK(s.select("0" ) == s.range(0, 1)); CHECK(s.select( "123") == s.range(1, 4)); CHECK(s.select( "23") == s.range(2, 4)); CHECK(s.select( "3") == s.range(3, 4)); } // find() { ryml::csubstr s012345 = "012345"; // find single characters: CHECK(s012345.find('a') == ryml::npos); CHECK(s012345.find('0' ) == 0u); CHECK(s012345.find('0', 1u) == ryml::npos); CHECK(s012345.find('1' ) == 1u); CHECK(s012345.find('1', 2u) == ryml::npos); CHECK(s012345.find('2' ) == 2u); CHECK(s012345.find('2', 3u) == ryml::npos); CHECK(s012345.find('3' ) == 3u); CHECK(s012345.find('3', 4u) == ryml::npos); // find patterns CHECK(s012345.find("ab" ) == ryml::npos); CHECK(s012345.find("01" ) == 0u); CHECK(s012345.find("01", 1u) == ryml::npos); CHECK(s012345.find("12" ) == 1u); CHECK(s012345.find("12", 2u) == ryml::npos); CHECK(s012345.find("23" ) == 2u); CHECK(s012345.find("23", 3u) == ryml::npos); } // count(): count the number of occurrences of a character { ryml::csubstr buf = "00110022003300440055"; CHECK(buf.count('1' ) == 2u); CHECK(buf.count('1', 0u) == 2u); CHECK(buf.count('1', 1u) == 2u); CHECK(buf.count('1', 2u) == 2u); CHECK(buf.count('1', 3u) == 1u); CHECK(buf.count('1', 4u) == 0u); CHECK(buf.count('1', 5u) == 0u); CHECK(buf.count('0' ) == 10u); CHECK(buf.count('0', 0u) == 10u); CHECK(buf.count('0', 1u) == 9u); CHECK(buf.count('0', 2u) == 8u); CHECK(buf.count('0', 3u) == 8u); CHECK(buf.count('0', 4u) == 8u); CHECK(buf.count('0', 5u) == 7u); CHECK(buf.count('0', 6u) == 6u); CHECK(buf.count('0', 7u) == 6u); CHECK(buf.count('0', 8u) == 6u); CHECK(buf.count('0', 9u) == 5u); CHECK(buf.count('0', 10u) == 4u); CHECK(buf.count('0', 11u) == 4u); CHECK(buf.count('0', 12u) == 4u); CHECK(buf.count('0', 13u) == 3u); CHECK(buf.count('0', 14u) == 2u); CHECK(buf.count('0', 15u) == 2u); CHECK(buf.count('0', 16u) == 2u); CHECK(buf.count('0', 17u) == 1u); CHECK(buf.count('0', 18u) == 0u); CHECK(buf.count('0', 19u) == 0u); CHECK(buf.count('0', 20u) == 0u); } // first_of(),last_of() { ryml::csubstr s012345 = "012345"; CHECK(s012345.first_of('a') == ryml::npos); CHECK(s012345.first_of("ab") == ryml::npos); CHECK(s012345.first_of('0') == 0u); CHECK(s012345.first_of("0") == 0u); CHECK(s012345.first_of("01") == 0u); CHECK(s012345.first_of("10") == 0u); CHECK(s012345.first_of("012") == 0u); CHECK(s012345.first_of("210") == 0u); CHECK(s012345.first_of("0123") == 0u); CHECK(s012345.first_of("3210") == 0u); CHECK(s012345.first_of("01234") == 0u); CHECK(s012345.first_of("43210") == 0u); CHECK(s012345.first_of("012345") == 0u); CHECK(s012345.first_of("543210") == 0u); CHECK(s012345.first_of('5') == 5u); CHECK(s012345.first_of("5") == 5u); CHECK(s012345.first_of("45") == 4u); CHECK(s012345.first_of("54") == 4u); CHECK(s012345.first_of("345") == 3u); CHECK(s012345.first_of("543") == 3u); CHECK(s012345.first_of("2345") == 2u); CHECK(s012345.first_of("5432") == 2u); CHECK(s012345.first_of("12345") == 1u); CHECK(s012345.first_of("54321") == 1u); CHECK(s012345.first_of("012345") == 0u); CHECK(s012345.first_of("543210") == 0u); CHECK(s012345.first_of('0', 6u) == ryml::npos); CHECK(s012345.first_of('5', 6u) == ryml::npos); CHECK(s012345.first_of("012345", 6u) == ryml::npos); // CHECK(s012345.last_of('a') == ryml::npos); CHECK(s012345.last_of("ab") == ryml::npos); CHECK(s012345.last_of('0') == 0u); CHECK(s012345.last_of("0") == 0u); CHECK(s012345.last_of("01") == 1u); CHECK(s012345.last_of("10") == 1u); CHECK(s012345.last_of("012") == 2u); CHECK(s012345.last_of("210") == 2u); CHECK(s012345.last_of("0123") == 3u); CHECK(s012345.last_of("3210") == 3u); CHECK(s012345.last_of("01234") == 4u); CHECK(s012345.last_of("43210") == 4u); CHECK(s012345.last_of("012345") == 5u); CHECK(s012345.last_of("543210") == 5u); CHECK(s012345.last_of('5') == 5u); CHECK(s012345.last_of("5") == 5u); CHECK(s012345.last_of("45") == 5u); CHECK(s012345.last_of("54") == 5u); CHECK(s012345.last_of("345") == 5u); CHECK(s012345.last_of("543") == 5u); CHECK(s012345.last_of("2345") == 5u); CHECK(s012345.last_of("5432") == 5u); CHECK(s012345.last_of("12345") == 5u); CHECK(s012345.last_of("54321") == 5u); CHECK(s012345.last_of("012345") == 5u); CHECK(s012345.last_of("543210") == 5u); CHECK(s012345.last_of('0', 6u) == 0u); CHECK(s012345.last_of('5', 6u) == 5u); CHECK(s012345.last_of("012345", 6u) == 5u); } // first_not_of(), last_not_of() { ryml::csubstr s012345 = "012345"; CHECK(s012345.first_not_of('a') == 0u); CHECK(s012345.first_not_of("ab") == 0u); CHECK(s012345.first_not_of('0') == 1u); CHECK(s012345.first_not_of("0") == 1u); CHECK(s012345.first_not_of("01") == 2u); CHECK(s012345.first_not_of("10") == 2u); CHECK(s012345.first_not_of("012") == 3u); CHECK(s012345.first_not_of("210") == 3u); CHECK(s012345.first_not_of("0123") == 4u); CHECK(s012345.first_not_of("3210") == 4u); CHECK(s012345.first_not_of("01234") == 5u); CHECK(s012345.first_not_of("43210") == 5u); CHECK(s012345.first_not_of("012345") == ryml::npos); CHECK(s012345.first_not_of("543210") == ryml::npos); CHECK(s012345.first_not_of('5') == 0u); CHECK(s012345.first_not_of("5") == 0u); CHECK(s012345.first_not_of("45") == 0u); CHECK(s012345.first_not_of("54") == 0u); CHECK(s012345.first_not_of("345") == 0u); CHECK(s012345.first_not_of("543") == 0u); CHECK(s012345.first_not_of("2345") == 0u); CHECK(s012345.first_not_of("5432") == 0u); CHECK(s012345.first_not_of("12345") == 0u); CHECK(s012345.first_not_of("54321") == 0u); CHECK(s012345.first_not_of("012345") == ryml::npos); CHECK(s012345.first_not_of("543210") == ryml::npos); CHECK(s012345.last_not_of('a') == 5u); CHECK(s012345.last_not_of("ab") == 5u); CHECK(s012345.last_not_of('5') == 4u); CHECK(s012345.last_not_of("5") == 4u); CHECK(s012345.last_not_of("45") == 3u); CHECK(s012345.last_not_of("54") == 3u); CHECK(s012345.last_not_of("345") == 2u); CHECK(s012345.last_not_of("543") == 2u); CHECK(s012345.last_not_of("2345") == 1u); CHECK(s012345.last_not_of("5432") == 1u); CHECK(s012345.last_not_of("12345") == 0u); CHECK(s012345.last_not_of("54321") == 0u); CHECK(s012345.last_not_of("012345") == ryml::npos); CHECK(s012345.last_not_of("543210") == ryml::npos); CHECK(s012345.last_not_of('0') == 5u); CHECK(s012345.last_not_of("0") == 5u); CHECK(s012345.last_not_of("01") == 5u); CHECK(s012345.last_not_of("10") == 5u); CHECK(s012345.last_not_of("012") == 5u); CHECK(s012345.last_not_of("210") == 5u); CHECK(s012345.last_not_of("0123") == 5u); CHECK(s012345.last_not_of("3210") == 5u); CHECK(s012345.last_not_of("01234") == 5u); CHECK(s012345.last_not_of("43210") == 5u); CHECK(s012345.last_not_of("012345") == ryml::npos); CHECK(s012345.last_not_of("543210") == ryml::npos); } // first_non_empty_span() { CHECK(ryml::csubstr("foo bar").first_non_empty_span() == "foo"); CHECK(ryml::csubstr(" foo bar").first_non_empty_span() == "foo"); CHECK(ryml::csubstr("\n \r \t foo bar").first_non_empty_span() == "foo"); CHECK(ryml::csubstr("\n \r \t foo\n\r\t bar").first_non_empty_span() == "foo"); CHECK(ryml::csubstr("\n \r \t foo\n\r\t bar").first_non_empty_span() == "foo"); CHECK(ryml::csubstr(",\n \r \t foo\n\r\t bar").first_non_empty_span() == ","); } // first_uint_span() { CHECK(ryml::csubstr("1234 asdkjh").first_uint_span() == "1234"); CHECK(ryml::csubstr("1234\rasdkjh").first_uint_span() == "1234"); CHECK(ryml::csubstr("1234\tasdkjh").first_uint_span() == "1234"); CHECK(ryml::csubstr("1234\nasdkjh").first_uint_span() == "1234"); CHECK(ryml::csubstr("1234]asdkjh").first_uint_span() == "1234"); CHECK(ryml::csubstr("1234)asdkjh").first_uint_span() == "1234"); CHECK(ryml::csubstr("1234gasdkjh").first_uint_span() == ""); } // first_int_span() { CHECK(ryml::csubstr("-1234 asdkjh").first_int_span() == "-1234"); CHECK(ryml::csubstr("-1234\rasdkjh").first_int_span() == "-1234"); CHECK(ryml::csubstr("-1234\tasdkjh").first_int_span() == "-1234"); CHECK(ryml::csubstr("-1234\nasdkjh").first_int_span() == "-1234"); CHECK(ryml::csubstr("-1234]asdkjh").first_int_span() == "-1234"); CHECK(ryml::csubstr("-1234)asdkjh").first_int_span() == "-1234"); CHECK(ryml::csubstr("-1234gasdkjh").first_int_span() == ""); } // first_real_span() { CHECK(ryml::csubstr("-1234 asdkjh").first_real_span() == "-1234"); CHECK(ryml::csubstr("-1234\rasdkjh").first_real_span() == "-1234"); CHECK(ryml::csubstr("-1234\tasdkjh").first_real_span() == "-1234"); CHECK(ryml::csubstr("-1234\nasdkjh").first_real_span() == "-1234"); CHECK(ryml::csubstr("-1234]asdkjh").first_real_span() == "-1234"); CHECK(ryml::csubstr("-1234)asdkjh").first_real_span() == "-1234"); CHECK(ryml::csubstr("-1234gasdkjh").first_real_span() == ""); CHECK(ryml::csubstr("1.234 asdkjh").first_real_span() == "1.234"); CHECK(ryml::csubstr("1.234e+5 asdkjh").first_real_span() == "1.234e+5"); CHECK(ryml::csubstr("1.234e-5 asdkjh").first_real_span() == "1.234e-5"); CHECK(ryml::csubstr("1.234 asdkjh").first_real_span() == "1.234"); CHECK(ryml::csubstr("1.234e+5 asdkjh").first_real_span() == "1.234e+5"); CHECK(ryml::csubstr("1.234e-5 asdkjh").first_real_span() == "1.234e-5"); CHECK(ryml::csubstr("-1.234 asdkjh").first_real_span() == "-1.234"); CHECK(ryml::csubstr("-1.234e+5 asdkjh").first_real_span() == "-1.234e+5"); CHECK(ryml::csubstr("-1.234e-5 asdkjh").first_real_span() == "-1.234e-5"); // hexadecimal real numbers CHECK(ryml::csubstr("0x1.e8480p+19 asdkjh").first_real_span() == "0x1.e8480p+19"); CHECK(ryml::csubstr("0x1.e8480p-19 asdkjh").first_real_span() == "0x1.e8480p-19"); CHECK(ryml::csubstr("-0x1.e8480p+19 asdkjh").first_real_span() == "-0x1.e8480p+19"); CHECK(ryml::csubstr("-0x1.e8480p-19 asdkjh").first_real_span() == "-0x1.e8480p-19"); CHECK(ryml::csubstr("+0x1.e8480p+19 asdkjh").first_real_span() == "+0x1.e8480p+19"); CHECK(ryml::csubstr("+0x1.e8480p-19 asdkjh").first_real_span() == "+0x1.e8480p-19"); // binary real numbers CHECK(ryml::csubstr("0b101.011p+19 asdkjh").first_real_span() == "0b101.011p+19"); CHECK(ryml::csubstr("0b101.011p-19 asdkjh").first_real_span() == "0b101.011p-19"); CHECK(ryml::csubstr("-0b101.011p+19 asdkjh").first_real_span() == "-0b101.011p+19"); CHECK(ryml::csubstr("-0b101.011p-19 asdkjh").first_real_span() == "-0b101.011p-19"); CHECK(ryml::csubstr("+0b101.011p+19 asdkjh").first_real_span() == "+0b101.011p+19"); CHECK(ryml::csubstr("+0b101.011p-19 asdkjh").first_real_span() == "+0b101.011p-19"); // octal real numbers CHECK(ryml::csubstr("0o173.045p+19 asdkjh").first_real_span() == "0o173.045p+19"); CHECK(ryml::csubstr("0o173.045p-19 asdkjh").first_real_span() == "0o173.045p-19"); CHECK(ryml::csubstr("-0o173.045p+19 asdkjh").first_real_span() == "-0o173.045p+19"); CHECK(ryml::csubstr("-0o173.045p-19 asdkjh").first_real_span() == "-0o173.045p-19"); CHECK(ryml::csubstr("+0o173.045p+19 asdkjh").first_real_span() == "+0o173.045p+19"); CHECK(ryml::csubstr("+0o173.045p-19 asdkjh").first_real_span() == "+0o173.045p-19"); } // see also is_number() // basename(), dirname(), extshort(), extlong() { CHECK(ryml::csubstr("/path/to/file.tar.gz").basename() == "file.tar.gz"); CHECK(ryml::csubstr("/path/to/file.tar.gz").dirname() == "/path/to/"); CHECK(ryml::csubstr("C:\\path\\to\\file.tar.gz").basename('\\') == "file.tar.gz"); CHECK(ryml::csubstr("C:\\path\\to\\file.tar.gz").dirname('\\') == "C:\\path\\to\\"); CHECK(ryml::csubstr("/path/to/file.tar.gz").extshort() == "gz"); CHECK(ryml::csubstr("/path/to/file.tar.gz").extlong() == "tar.gz"); CHECK(ryml::csubstr("/path/to/file.tar.gz").name_wo_extshort() == "/path/to/file.tar"); CHECK(ryml::csubstr("/path/to/file.tar.gz").name_wo_extlong() == "/path/to/file"); } // split() { using namespace ryml; csubstr parts[] = {"aa", "bb", "cc", "dd", "ee", "ff"}; { size_t count = 0; for(csubstr part : csubstr("aa/bb/cc/dd/ee/ff").split('/')) CHECK(part == parts[count++]); CHECK(count == 6u); } { size_t count = 0; for(csubstr part : csubstr("aa.bb.cc.dd.ee.ff").split('.')) CHECK(part == parts[count++]); CHECK(count == 6u); } { size_t count = 0; for(csubstr part : csubstr("aa-bb-cc-dd-ee-ff").split('-')) CHECK(part == parts[count++]); CHECK(count == 6u); } // see also next_split() } // pop_left(), pop_right() --- non-greedy version // gpop_left(), gpop_right() --- greedy version { const bool skip_empty = true; // pop_left(): pop the last element from the left CHECK(ryml::csubstr( "0/1/2" ). pop_left('/' ) == "0" ); CHECK(ryml::csubstr( "/0/1/2" ). pop_left('/' ) == "" ); CHECK(ryml::csubstr("//0/1/2" ). pop_left('/' ) == "" ); CHECK(ryml::csubstr( "0/1/2" ). pop_left('/', skip_empty) == "0" ); CHECK(ryml::csubstr( "/0/1/2" ). pop_left('/', skip_empty) == "/0" ); CHECK(ryml::csubstr("//0/1/2" ). pop_left('/', skip_empty) == "//0" ); // gpop_left(): pop all but the first element (greedy pop) CHECK(ryml::csubstr( "0/1/2" ).gpop_left('/' ) == "0/1" ); CHECK(ryml::csubstr( "/0/1/2" ).gpop_left('/' ) == "/0/1" ); CHECK(ryml::csubstr("//0/1/2" ).gpop_left('/' ) == "//0/1" ); CHECK(ryml::csubstr( "0/1/2/" ).gpop_left('/' ) == "0/1/2"); CHECK(ryml::csubstr( "/0/1/2/" ).gpop_left('/' ) == "/0/1/2"); CHECK(ryml::csubstr("//0/1/2/" ).gpop_left('/' ) == "//0/1/2"); CHECK(ryml::csubstr( "0/1/2//" ).gpop_left('/' ) == "0/1/2/"); CHECK(ryml::csubstr( "/0/1/2//" ).gpop_left('/' ) == "/0/1/2/"); CHECK(ryml::csubstr("//0/1/2//" ).gpop_left('/' ) == "//0/1/2/"); CHECK(ryml::csubstr( "0/1/2" ).gpop_left('/', skip_empty) == "0/1" ); CHECK(ryml::csubstr( "/0/1/2" ).gpop_left('/', skip_empty) == "/0/1" ); CHECK(ryml::csubstr("//0/1/2" ).gpop_left('/', skip_empty) == "//0/1" ); CHECK(ryml::csubstr( "0/1/2/" ).gpop_left('/', skip_empty) == "0/1" ); CHECK(ryml::csubstr( "/0/1/2/" ).gpop_left('/', skip_empty) == "/0/1" ); CHECK(ryml::csubstr("//0/1/2/" ).gpop_left('/', skip_empty) == "//0/1" ); CHECK(ryml::csubstr( "0/1/2//" ).gpop_left('/', skip_empty) == "0/1" ); CHECK(ryml::csubstr( "/0/1/2//" ).gpop_left('/', skip_empty) == "/0/1" ); CHECK(ryml::csubstr("//0/1/2//" ).gpop_left('/', skip_empty) == "//0/1" ); // pop_right(): pop the last element from the right CHECK(ryml::csubstr( "0/1/2" ). pop_right('/' ) == "2" ); CHECK(ryml::csubstr( "0/1/2/" ). pop_right('/' ) == "" ); CHECK(ryml::csubstr( "0/1/2//" ). pop_right('/' ) == "" ); CHECK(ryml::csubstr( "0/1/2" ). pop_right('/', skip_empty) == "2" ); CHECK(ryml::csubstr( "0/1/2/" ). pop_right('/', skip_empty) == "2/" ); CHECK(ryml::csubstr( "0/1/2//" ). pop_right('/', skip_empty) == "2//" ); // gpop_right(): pop all but the first element (greedy pop) CHECK(ryml::csubstr( "0/1/2" ).gpop_right('/' ) == "1/2"); CHECK(ryml::csubstr( "0/1/2/" ).gpop_right('/' ) == "1/2/" ); CHECK(ryml::csubstr( "0/1/2//" ).gpop_right('/' ) == "1/2//" ); CHECK(ryml::csubstr( "/0/1/2" ).gpop_right('/' ) == "0/1/2"); CHECK(ryml::csubstr( "/0/1/2/" ).gpop_right('/' ) == "0/1/2/" ); CHECK(ryml::csubstr( "/0/1/2//" ).gpop_right('/' ) == "0/1/2//" ); CHECK(ryml::csubstr("//0/1/2" ).gpop_right('/' ) == "/0/1/2"); CHECK(ryml::csubstr("//0/1/2/" ).gpop_right('/' ) == "/0/1/2/" ); CHECK(ryml::csubstr("//0/1/2//" ).gpop_right('/' ) == "/0/1/2//" ); CHECK(ryml::csubstr( "0/1/2" ).gpop_right('/', skip_empty) == "1/2"); CHECK(ryml::csubstr( "0/1/2/" ).gpop_right('/', skip_empty) == "1/2/" ); CHECK(ryml::csubstr( "0/1/2//" ).gpop_right('/', skip_empty) == "1/2//" ); CHECK(ryml::csubstr( "/0/1/2" ).gpop_right('/', skip_empty) == "1/2"); CHECK(ryml::csubstr( "/0/1/2/" ).gpop_right('/', skip_empty) == "1/2/" ); CHECK(ryml::csubstr( "/0/1/2//" ).gpop_right('/', skip_empty) == "1/2//" ); CHECK(ryml::csubstr("//0/1/2" ).gpop_right('/', skip_empty) == "1/2"); CHECK(ryml::csubstr("//0/1/2/" ).gpop_right('/', skip_empty) == "1/2/" ); CHECK(ryml::csubstr("//0/1/2//" ).gpop_right('/', skip_empty) == "1/2//" ); } } //----------------------------------------------------------------------------- /** demonstrate how to load a YAML file from disk to parse with ryml. * * ryml offers no overload to directly parse files from disk; it only * parses source buffers (which may be mutable or immutable). It is * up to the caller to load the file contents into a buffer before * parsing with ryml. * * But that does not mean that loading a file is unimportant. There * are many ways to achieve this in C++, but for convenience and to * enable you to quickly get up to speed, here is an example * implementation loading a file from disk and then parsing the * resulting buffer with ryml. * @see doc_parse */ void sample_parse_file() { const char filename[] = "ryml_example.yml"; // because this is a minimal sample, it assumes nothing on the // environment/OS (other than that it can read/write files). So we // create the file on the fly: file_put_contents(filename, ryml::csubstr(R"( foo: 1 bar: - 2 - 3 )")); // now we can load it into a std::string (for example): { std::string contents = file_get_contents(filename); ryml::Tree tree = ryml::parse_in_arena(ryml::to_csubstr(contents)); // immutable (csubstr) overload CHECK(tree["foo"].val() == "1"); CHECK(tree["bar"][0].val() == "2"); CHECK(tree["bar"][1].val() == "3"); } // or we can use a vector instead: { std::vector contents = file_get_contents>(filename); ryml::Tree tree = ryml::parse_in_place(ryml::to_substr(contents)); // mutable (csubstr) overload CHECK(tree["foo"].val() == "1"); CHECK(tree["bar"][0].val() == "2"); CHECK(tree["bar"][1].val() == "3"); } // generally, any contiguous char container can be used with ryml, // provided that the ryml::substr/ryml::csubstr view can be // created out of it. // // ryml provides the overloads above for these two containers, but // if you are using another container it should be very easy (only // requires pointer and length). } //----------------------------------------------------------------------------- /** demonstrate in-place parsing of a mutable YAML source buffer. * @see doc_parse */ void sample_parse_in_place() { // Like the name suggests, parse_in_place() directly mutates the // source buffer in place char src[] = "{foo: 1, bar: [2, 3]}"; // ryml can parse in situ ryml::substr srcview = src; // a mutable view to the source buffer ryml::Tree tree = ryml::parse_in_place(srcview); // you can also reuse the tree and/or parser ryml::ConstNodeRef root = tree.crootref(); // get a constant reference to the root CHECK(root.is_map()); CHECK(root["foo"].is_keyval()); CHECK(root["foo"].key() == "foo"); CHECK(root["foo"].val() == "1"); CHECK(root["bar"].is_seq()); CHECK(root["bar"].has_key()); CHECK(root["bar"].key() == "bar"); CHECK(root["bar"][0].val() == "2"); CHECK(root["bar"][1].val() == "3"); // deserializing: int foo = 0, bar0 = 0, bar1 = 0; root["foo"] >> foo; root["bar"][0] >> bar0; root["bar"][1] >> bar1; CHECK(foo == 1); CHECK(bar0 == 2); CHECK(bar1 == 3); // after parsing, the tree holds views to the source buffer: CHECK(root["foo"].val().data() == src + strlen("{foo: ")); CHECK(root["foo"].val().begin() == src + strlen("{foo: ")); CHECK(root["foo"].val().end() == src + strlen("{foo: 1")); CHECK(root["foo"].val().is_sub(srcview)); // equivalent to the previous three assertions CHECK(root["bar"][0].val().data() == src + strlen("{foo: 1, bar: [")); CHECK(root["bar"][0].val().begin() == src + strlen("{foo: 1, bar: [")); CHECK(root["bar"][0].val().end() == src + strlen("{foo: 1, bar: [2")); CHECK(root["bar"][0].val().is_sub(srcview)); // equivalent to the previous three assertions CHECK(root["bar"][1].val().data() == src + strlen("{foo: 1, bar: [2, ")); CHECK(root["bar"][1].val().begin() == src + strlen("{foo: 1, bar: [2, ")); CHECK(root["bar"][1].val().end() == src + strlen("{foo: 1, bar: [2, 3")); CHECK(root["bar"][1].val().is_sub(srcview)); // equivalent to the previous three assertions // NOTE. parse_in_place() cannot accept ryml::csubstr // so this will cause a /compile/ error: ryml::csubstr csrcview = srcview; // ok, can assign from mutable to immutable //tree = ryml::parse_in_place(csrcview); // compile error, cannot mutate an immutable view (void)csrcview; } //----------------------------------------------------------------------------- /** demonstrate parsing of a read-only YAML source buffer * @see doc_parse */ void sample_parse_in_arena() { // to parse read-only memory, ryml will copy first to the tree's // arena, and then parse the copied buffer: ryml::Tree tree = ryml::parse_in_arena("{foo: 1, bar: [2, 3]}"); ryml::ConstNodeRef root = tree.crootref(); // get a const reference to the root CHECK(root.is_map()); CHECK(root["foo"].is_keyval()); CHECK(root["foo"].key() == "foo"); CHECK(root["foo"].val() == "1"); CHECK(root["bar"].is_seq()); CHECK(root["bar"].has_key()); CHECK(root["bar"].key() == "bar"); CHECK(root["bar"][0].val() == "2"); CHECK(root["bar"][1].val() == "3"); // deserializing: int foo = 0, bar0 = 0, bar1 = 0; root["foo"] >> foo; root["bar"][0] >> bar0; root["bar"][1] >> bar1; CHECK(foo == 1); CHECK(bar0 == 2); CHECK(bar1 == 3); // NOTE. parse_in_arena() cannot accept ryml::substr. Overloads // receiving substr buffers are declared, but intentionally left // undefined, so this will cause a /linker/ error char src[] = "{foo: is it really true}"; ryml::substr srcview = src; //tree = ryml::parse_in_place(srcview); // linker error, overload intentionally undefined // If you really intend to parse a mutable buffer in the arena, // then simply convert it to immutable prior to calling // parse_in_arena(): ryml::csubstr csrcview = srcview; // assigning from src also works tree = ryml::parse_in_arena(csrcview); // OK! csrcview is immutable CHECK(tree["foo"].val() == "is it really true"); } //----------------------------------------------------------------------------- /** demonstrate reuse/modification of tree when parsing * @see doc_parse */ void sample_parse_reuse_tree() { ryml::Tree tree; // it will always be faster if the tree's size is conveniently reserved: tree.reserve(30); // reserve 30 nodes (good enough for this sample) // if you are using the tree's arena to serialize data, // then reserve also the arena's size: tree.reserve_arena(256); // reserve 256 characters (good enough for this sample) // now parse into the tree: ryml::csubstr yaml = R"(foo: 1 bar: [2, 3] )"; ryml::parse_in_arena(yaml, &tree); ryml::ConstNodeRef root = tree.crootref(); CHECK(root.num_children() == 2); CHECK(root.is_map()); CHECK(root["foo"].is_keyval()); CHECK(root["foo"].key() == "foo"); CHECK(root["foo"].val() == "1"); CHECK(root["bar"].is_seq()); CHECK(root["bar"].has_key()); CHECK(root["bar"].key() == "bar"); CHECK(root["bar"][0].val() == "2"); CHECK(root["bar"][1].val() == "3"); CHECK(ryml::emitrs_yaml(tree) == R"(foo: 1 bar: [2,3] )"); // WATCHOUT: parsing into an existing tree will APPEND to it: ryml::parse_in_arena("{foo2: 12, bar2: [22, 32]}", &tree); CHECK(ryml::emitrs_yaml(tree) == R"(foo: 1 bar: [2,3] foo2: 12 bar2: [22,32] )"); CHECK(root.num_children() == 4); CHECK(root["foo2"].is_keyval()); CHECK(root["foo2"].key() == "foo2"); CHECK(root["foo2"].val() == "12"); CHECK(root["bar2"].is_seq()); CHECK(root["bar2"].has_key()); CHECK(root["bar2"].key() == "bar2"); CHECK(root["bar2"][0].val() == "22"); CHECK(root["bar2"][1].val() == "32"); // clear first before parsing into an existing tree. tree.clear(); tree.clear_arena(); // you may or may not want to clear the arena ryml::parse_in_arena("- a\n- b\n- {x0: 1, x1: 2}", &tree); CHECK(ryml::emitrs_yaml(tree) == "- a\n- b\n- {x0: 1,x1: 2}\n"); CHECK(root.is_seq()); CHECK(root[0].val() == "a"); CHECK(root[1].val() == "b"); CHECK(root[2].is_map()); CHECK(root[2]["x0"].val() == "1"); CHECK(root[2]["x1"].val() == "2"); // we can parse directly into a node nested deep in an existing tree: ryml::NodeRef mroot = tree.rootref(); // modifiable root ryml::parse_in_arena("champagne: Dom Perignon\ncoffee: Arabica", mroot.append_child()); CHECK(ryml::emitrs_yaml(tree) == R"(- a - b - {x0: 1,x1: 2} - champagne: Dom Perignon coffee: Arabica )"); CHECK(root.is_seq()); CHECK(root[0].val() == "a"); CHECK(root[1].val() == "b"); CHECK(root[2].is_map()); CHECK(root[2]["x0"].val() == "1"); CHECK(root[2]["x1"].val() == "2"); CHECK(root[3].is_map()); CHECK(root[3]["champagne"].val() == "Dom Perignon"); CHECK(root[3]["coffee"].val() == "Arabica"); // watchout: to add to an existing node within a map, the node's // key must be separately set first: ryml::NodeRef more = mroot[3].append_child({ryml::KEYMAP, "more"}); ryml::NodeRef beer = mroot[3].append_child({ryml::KEYSEQ, "beer"}); ryml::NodeRef always = mroot[3].append_child({ryml::KEY, "always"}); ryml::parse_in_arena("{vinho verde: Soalheiro, vinho tinto: Redoma 2017}", more); ryml::parse_in_arena("- Rochefort 10\n- Busch\n- Leffe Rituel", beer); ryml::parse_in_arena("lots\nof\nwater", always); CHECK(ryml::emitrs_yaml(tree) == R"(- a - b - {x0: 1,x1: 2} - champagne: Dom Perignon coffee: Arabica more: vinho verde: Soalheiro vinho tinto: Redoma 2017 beer: - Rochefort 10 - Busch - Leffe Rituel always: lots of water )"); // can append at the top: ryml::parse_in_arena("- foo\n- bar\n- baz\n- bat", mroot); CHECK(ryml::emitrs_yaml(tree) == R"(- a - b - {x0: 1,x1: 2} - champagne: Dom Perignon coffee: Arabica more: vinho verde: Soalheiro vinho tinto: Redoma 2017 beer: - Rochefort 10 - Busch - Leffe Rituel always: lots of water - foo - bar - baz - bat )"); // or nested: ryml::parse_in_arena("[Kasteel Donker]", beer); CHECK(ryml::emitrs_yaml(tree) == R"(- a - b - {x0: 1,x1: 2} - champagne: Dom Perignon coffee: Arabica more: vinho verde: Soalheiro vinho tinto: Redoma 2017 beer: - Rochefort 10 - Busch - Leffe Rituel - Kasteel Donker always: lots of water - foo - bar - baz - bat )"); } //----------------------------------------------------------------------------- /** Demonstrates reuse of an existing parser. Doing this is * recommended when multiple files are parsed. * @see doc_parse */ void sample_parse_reuse_parser() { ryml::EventHandlerTree evt_handler = {}; ryml::Parser parser(&evt_handler); // it is also advised to reserve the parser depth // to the expected depth of the data tree: parser.reserve_stack(10); // uses small storage optimization // defaulting to 16 depth, so this // instruction is a no-op, and the stack // will located in the parser object. parser.reserve_stack(20); // But this will cause an allocation // because it is above 16. ryml::Tree champagnes = parse_in_arena(&parser, "champagnes.yml", "[Dom Perignon, Gosset Grande Reserve, Jacquesson 742]"); CHECK(ryml::emitrs_yaml(champagnes) == "[Dom Perignon,Gosset Grande Reserve,Jacquesson 742]"); ryml::Tree beers = parse_in_arena(&parser, "beers.yml", "[Rochefort 10, Busch, Leffe Rituel, Kasteel Donker]"); CHECK(ryml::emitrs_yaml(beers) == "[Rochefort 10,Busch,Leffe Rituel,Kasteel Donker]"); } //----------------------------------------------------------------------------- /** for ultimate speed when parsing multiple times, reuse both the * tree and parser * @see doc_parse */ void sample_parse_reuse_tree_and_parser() { ryml::Tree tree; // it will always be faster if the tree's size is conveniently reserved: tree.reserve(30); // reserve 30 nodes (good enough for this sample) // if you are using the tree's arena to serialize data, // then reserve also the arena's size: tree.reserve(256); // reserve 256 characters (good enough for this sample) ryml::EventHandlerTree evt_handler; ryml::Parser parser(&evt_handler); // it is also advised to reserve the parser depth // to the expected depth of the data tree: parser.reserve_stack(10); // the parser uses small storage // optimization defaulting to 16 depth, // so this instruction is a no-op, and // the stack will be located in the // parser object. parser.reserve_stack(20); // But this will cause an allocation // because it is above 16. ryml::csubstr champagnes = "- Dom Perignon\n- Gosset Grande Reserve\n- Jacquesson 742"; ryml::csubstr beers = "- Rochefort 10\n- Busch\n- Leffe Rituel\n- Kasteel Donker"; ryml::csubstr wines = "- Soalheiro\n- Niepoort Redoma 2017\n- Vina Esmeralda"; parse_in_arena(&parser, "champagnes.yml", champagnes, &tree); CHECK(ryml::emitrs_yaml(tree) == R"(- Dom Perignon - Gosset Grande Reserve - Jacquesson 742 )"); // watchout: this will APPEND to the given tree: parse_in_arena(&parser, "beers.yml", beers, &tree); CHECK(ryml::emitrs_yaml(tree) == R"(- Dom Perignon - Gosset Grande Reserve - Jacquesson 742 - Rochefort 10 - Busch - Leffe Rituel - Kasteel Donker )"); // if you don't wish to append, clear the tree first: tree.clear(); parse_in_arena(&parser, "wines.yml", wines, &tree); CHECK(ryml::emitrs_yaml(tree) == R"(- Soalheiro - Niepoort Redoma 2017 - Vina Esmeralda )"); } //----------------------------------------------------------------------------- /** shows how to programatically iterate through trees * @see doc_tree * @see doc_node_classes */ void sample_iterate_trees() { const ryml::Tree tree = ryml::parse_in_arena(R"(doe: "a deer, a female deer" ray: "a drop of golden sun" pi: 3.14159 xmas: true french-hens: 3 calling-birds: - huey - dewey - louie - fred xmas-fifth-day: calling-birds: four french-hens: 3 golden-rings: 5 partridges: count: 1 location: a pear tree turtle-doves: two cars: GTO )"); ryml::ConstNodeRef root = tree.crootref(); // iterate children { std::vector keys, vals; // to store all the root-level keys, vals for(ryml::ConstNodeRef n : root.children()) { keys.emplace_back(n.key()); vals.emplace_back(n.has_val() ? n.val() : ryml::csubstr{}); } CHECK(keys[0] == "doe"); CHECK(vals[0] == "a deer, a female deer"); CHECK(keys[1] == "ray"); CHECK(vals[1] == "a drop of golden sun"); CHECK(keys[2] == "pi"); CHECK(vals[2] == "3.14159"); CHECK(keys[3] == "xmas"); CHECK(vals[3] == "true"); CHECK(root[5].has_key()); CHECK(root[5].is_seq()); CHECK(root[5].key() == "calling-birds"); CHECK(!root[5].has_val()); // it is a map, so not a val //CHECK(root[5].val() == ""); // ERROR! node does not have a val. CHECK(keys[5] == "calling-birds"); CHECK(vals[5] == ""); } // iterate siblings { size_t count = 0; ryml::csubstr calling_birds[] = {"huey", "dewey", "louie", "fred"}; for(ryml::ConstNodeRef n : root["calling-birds"][2].siblings()) CHECK(n.val() == calling_birds[count++]); CHECK(count == 4u); } } //----------------------------------------------------------------------------- /** shows how to programatically create trees * @see doc_tree * @see doc_node_classes * */ void sample_create_trees() { ryml::NodeRef doe; CHECK(doe.invalid()); // it's pointing at nowhere ryml::Tree tree; ryml::NodeRef root = tree.rootref(); root |= ryml::MAP; // mark root as a map doe = root["doe"]; CHECK(!doe.invalid()); // it's now pointing at the tree CHECK(doe.is_seed()); // but the tree has nothing there, so this is only a seed // set the value of the node const char a_deer[] = "a deer, a female deer"; doe = a_deer; // now the node really exists in the tree, and this ref is no // longer a seed: CHECK(!doe.is_seed()); // WATCHOUT for lifetimes: CHECK(doe.val().str == a_deer); // it is pointing at the initial string // If you need to avoid lifetime dependency, serialize the data: { std::string a_drop = "a drop of golden sun"; // this will copy the string to the tree's arena: // (see the serialization samples below) root["ray"] << a_drop; // and now you can modify the original string without changing // the tree: a_drop[0] = 'Z'; a_drop[1] = 'Z'; } CHECK(root["ray"].val() == "a drop of golden sun"); // etc. root["pi"] << ryml::fmt::real(3.141592654, 5); root["xmas"] << ryml::fmt::boolalpha(true); root["french-hens"] << 3; ryml::NodeRef calling_birds = root["calling-birds"]; calling_birds |= ryml::SEQ; calling_birds.append_child() = "huey"; calling_birds.append_child() = "dewey"; calling_birds.append_child() = "louie"; calling_birds.append_child() = "fred"; ryml::NodeRef xmas5 = root["xmas-fifth-day"]; xmas5 |= ryml::MAP; xmas5["calling-birds"] = "four"; xmas5["french-hens"] << 3; xmas5["golden-rings"] << 5; xmas5["partridges"] |= ryml::MAP; xmas5["partridges"]["count"] << 1; xmas5["partridges"]["location"] = "a pear tree"; xmas5["turtle-doves"] = "two"; root["cars"] = "GTO"; CHECK(ryml::emitrs_yaml(tree) == R"(doe: 'a deer, a female deer' ray: a drop of golden sun pi: 3.14159 xmas: true french-hens: 3 calling-birds: - huey - dewey - louie - fred xmas-fifth-day: calling-birds: four french-hens: 3 golden-rings: 5 partridges: count: 1 location: a pear tree turtle-doves: two cars: GTO )"); } //----------------------------------------------------------------------------- /** demonstrates explicit and implicit interaction with the tree's string arena. * Notice that ryml only holds strings in the tree's nodes. */ void sample_tree_arena() { // mutable buffers are parsed in situ: { char buf[] = "[a, b, c, d]"; ryml::substr yml = buf; ryml::Tree tree = ryml::parse_in_place(yml); // notice the arena is empty: CHECK(tree.arena().empty()); // and the tree is pointing at the original buffer: ryml::NodeRef root = tree.rootref(); CHECK(root[0].val().is_sub(yml)); CHECK(root[1].val().is_sub(yml)); CHECK(root[2].val().is_sub(yml)); CHECK(root[3].val().is_sub(yml)); CHECK(yml.is_super(root[0].val())); CHECK(yml.is_super(root[1].val())); CHECK(yml.is_super(root[2].val())); CHECK(yml.is_super(root[3].val())); } // when parsing immutable buffers, the buffer is first copied to the // tree's arena; the copy in the arena is then the buffer which is // actually parsed { ryml::csubstr yml = "[a, b, c, d]"; ryml::Tree tree = ryml::parse_in_arena(yml); // notice the buffer was copied to the arena: CHECK(tree.arena().data() != yml.data()); CHECK(tree.arena() == yml); // and the tree is pointing at the arena instead of to the // original buffer: ryml::NodeRef root = tree.rootref(); ryml::csubstr arena = tree.arena(); CHECK(root[0].val().is_sub(arena)); CHECK(root[1].val().is_sub(arena)); CHECK(root[2].val().is_sub(arena)); CHECK(root[3].val().is_sub(arena)); CHECK(arena.is_super(root[0].val())); CHECK(arena.is_super(root[1].val())); CHECK(arena.is_super(root[2].val())); CHECK(arena.is_super(root[3].val())); } // the arena is also used when the data is serialized to string // with NodeRef::operator<<(): mutable buffer { char buf[] = "[a, b, c, d]"; // mutable ryml::substr yml = buf; ryml::Tree tree = ryml::parse_in_place(yml); // notice the arena is empty: CHECK(tree.arena().empty()); ryml::NodeRef root = tree.rootref(); // serialize an integer, and mutate the tree CHECK(root[2].val() == "c"); CHECK(root[2].val().is_sub(yml)); // val is first pointing at the buffer root[2] << 12345; CHECK(root[2].val() == "12345"); CHECK(root[2].val().is_sub(tree.arena())); // now val is pointing at the arena // notice the serialized string was appended to the tree's arena: CHECK(tree.arena() == "12345"); // serialize an integer, and mutate the tree CHECK(root[3].val() == "d"); CHECK(root[3].val().is_sub(yml)); // val is first pointing at the buffer root[3] << 67890; CHECK(root[3].val() == "67890"); CHECK(root[3].val().is_sub(tree.arena())); // now val is pointing at the arena // notice the serialized string was appended to the tree's arena: CHECK(tree.arena() == "1234567890"); } // the arena is also used when the data is serialized to string // with NodeRef::operator<<(): immutable buffer { ryml::csubstr yml = "[a, b, c, d]"; // immutable ryml::Tree tree = ryml::parse_in_arena(yml); // notice the buffer was copied to the arena: CHECK(tree.arena().data() != yml.data()); CHECK(tree.arena() == yml); ryml::NodeRef root = tree.rootref(); // serialize an integer, and mutate the tree CHECK(root[2].val() == "c"); root[2] << 12345; // serialize an integer CHECK(root[2].val() == "12345"); // notice the serialized string was appended to the tree's arena: // notice also the previous values remain there. // RYML DOES NOT KEEP TRACK OF REFERENCES TO THE ARENA. CHECK(tree.arena() == "[a, b, c, d]12345"); // old values: --------------^ // serialize an integer, and mutate the tree root[3] << 67890; CHECK(root[3].val() == "67890"); // notice the serialized string was appended to the tree's arena: // notice also the previous values remain there. // RYML DOES NOT KEEP TRACK OF REFERENCES TO THE ARENA. CHECK(tree.arena() == "[a, b, c, d]1234567890"); // old values: --------------^ ---^^^^^ } // to_arena(): directly serialize values to the arena: { ryml::Tree tree = ryml::parse_in_arena("{a: b}"); ryml::csubstr c10 = tree.to_arena(10101010); CHECK(c10 == "10101010"); CHECK(c10.is_sub(tree.arena())); CHECK(tree.arena() == "{a: b}10101010"); CHECK(tree.key(1) == "a"); CHECK(tree.val(1) == "b"); tree.set_val(1, c10); CHECK(tree.val(1) == c10); // and you can also do it through a node: ryml::NodeRef root = tree.rootref(); root["a"].set_val_serialized(2222); CHECK(root["a"].val() == "2222"); CHECK(tree.arena() == "{a: b}101010102222"); } // copy_to_arena(): manually copy a string to the arena: { ryml::Tree tree = ryml::parse_in_arena("{a: b}"); ryml::csubstr mystr = "Gosset Grande Reserve"; ryml::csubstr copied = tree.copy_to_arena(mystr); CHECK(!copied.overlaps(mystr)); CHECK(copied == mystr); CHECK(tree.arena() == "{a: b}Gosset Grande Reserve"); } // alloc_arena(): allocate a buffer from the arena: { ryml::Tree tree = ryml::parse_in_arena("{a: b}"); ryml::csubstr mystr = "Gosset Grande Reserve"; ryml::substr copied = tree.alloc_arena(mystr.size()); CHECK(!copied.overlaps(mystr)); memcpy(copied.str, mystr.str, mystr.len); CHECK(copied == mystr); CHECK(tree.arena() == "{a: b}Gosset Grande Reserve"); } // reserve_arena(): ensure the arena has a certain size to avoid reallocations { ryml::Tree tree = ryml::parse_in_arena("{a: b}"); CHECK(tree.arena().size() == strlen("{a: b}")); tree.reserve_arena(100); CHECK(tree.arena_capacity() >= 100); CHECK(tree.arena().size() == strlen("{a: b}")); tree.to_arena(123456); CHECK(tree.arena().first(12) == "{a: b}123456"); } } //----------------------------------------------------------------------------- /** ryml provides facilities for serializing and deserializing the C++ fundamental types, including boolean and null values; this is provided by the several overloads in @ref doc_to_chars and @ref doc_from_chars. To add serialization for user scalar types (ie, those types that should be serialized as strings in leaf nodes), you just need to define the appropriate overloads of to_chars and from_chars for those types; see @ref sample_user_scalar_types for an example on how to achieve this, and see @ref doc_serialization for more information on serialization. */ void sample_fundamental_types() { ryml::Tree tree; CHECK(tree.arena().empty()); CHECK(tree.to_arena('a') == "a"); CHECK(tree.arena() == "a"); CHECK(tree.to_arena("bcde") == "bcde"); CHECK(tree.arena() == "abcde"); CHECK(tree.to_arena(unsigned(0)) == "0"); CHECK(tree.arena() == "abcde0"); CHECK(tree.to_arena(int(1)) == "1"); CHECK(tree.arena() == "abcde01"); CHECK(tree.to_arena(uint8_t(0)) == "0"); CHECK(tree.arena() == "abcde010"); CHECK(tree.to_arena(uint16_t(1)) == "1"); CHECK(tree.arena() == "abcde0101"); CHECK(tree.to_arena(uint32_t(2)) == "2"); CHECK(tree.arena() == "abcde01012"); CHECK(tree.to_arena(uint64_t(3)) == "3"); CHECK(tree.arena() == "abcde010123"); CHECK(tree.to_arena(int8_t( 4)) == "4"); CHECK(tree.arena() == "abcde0101234"); CHECK(tree.to_arena(int8_t(-4)) == "-4"); CHECK(tree.arena() == "abcde0101234-4"); CHECK(tree.to_arena(int16_t( 5)) == "5"); CHECK(tree.arena() == "abcde0101234-45"); CHECK(tree.to_arena(int16_t(-5)) == "-5"); CHECK(tree.arena() == "abcde0101234-45-5"); CHECK(tree.to_arena(int32_t( 6)) == "6"); CHECK(tree.arena() == "abcde0101234-45-56"); CHECK(tree.to_arena(int32_t(-6)) == "-6"); CHECK(tree.arena() == "abcde0101234-45-56-6"); CHECK(tree.to_arena(int64_t( 7)) == "7"); CHECK(tree.arena() == "abcde0101234-45-56-67"); CHECK(tree.to_arena(int64_t(-7)) == "-7"); CHECK(tree.arena() == "abcde0101234-45-56-67-7"); CHECK(tree.to_arena((void*)1) == "0x1"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x1"); CHECK(tree.to_arena(float(0.124)) == "0.124"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.124"); CHECK(tree.to_arena(double(0.234)) == "0.234"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.234"); // write boolean values - see also sample_formatting() CHECK(tree.to_arena(bool(true)) == "1"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.2341"); CHECK(tree.to_arena(bool(false)) == "0"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410"); CHECK(tree.to_arena(c4::fmt::boolalpha(true)) == "true"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410true"); CHECK(tree.to_arena(c4::fmt::boolalpha(false)) == "false"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410truefalse"); // write special float values // see also sample_float_precision() const float fnan = std::numeric_limits::quiet_NaN(); const double dnan = std::numeric_limits::quiet_NaN(); const float finf = std::numeric_limits::infinity(); const double dinf = std::numeric_limits::infinity(); CHECK(tree.to_arena( finf) == ".inf"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410truefalse.inf"); CHECK(tree.to_arena( dinf) == ".inf"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410truefalse.inf.inf"); CHECK(tree.to_arena(-finf) == "-.inf"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410truefalse.inf.inf-.inf"); CHECK(tree.to_arena(-dinf) == "-.inf"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410truefalse.inf.inf-.inf-.inf"); CHECK(tree.to_arena( fnan) == ".nan"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410truefalse.inf.inf-.inf-.inf.nan"); CHECK(tree.to_arena( dnan) == ".nan"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410truefalse.inf.inf-.inf-.inf.nan.nan"); // read special float values // see also sample_float_precision() C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH("-Wfloat-equal"); tree = ryml::parse_in_arena(R"({ninf: -.inf, pinf: .inf, nan: .nan})"); float f = 0.f; double d = 0.; CHECK(f == 0.f); CHECK(d == 0.); tree["ninf"] >> f; CHECK(f == -finf); tree["ninf"] >> d; CHECK(d == -dinf); tree["pinf"] >> f; CHECK(f == finf); tree["pinf"] >> d; CHECK(d == dinf); tree["nan" ] >> f; CHECK(std::isnan(f)); tree["nan" ] >> d; CHECK(std::isnan(d)); C4_SUPPRESS_WARNING_GCC_CLANG_POP // value overflow detection: // (for integral types only) { // we will be detecting errors below, so we use this sample helper ScopedErrorHandlerExample err = {}; ryml::Tree t(err.callbacks()); // instantiate with the error-detecting callbacks // create a simple tree with an int value ryml::parse_in_arena(R"({val: 258})", &t); // by default, overflow is not detected: uint8_t valu8 = 0; int8_t vali8 = 0; t["val"] >> valu8; CHECK(valu8 == 2); // not 257; it wrapped around t["val"] >> vali8; CHECK(vali8 == 2); // not 257; it wrapped around // ...but there are facilities to detect overflow CHECK(ryml::overflows(t["val"].val())); CHECK(ryml::overflows(t["val"].val())); CHECK( ! ryml::overflows(t["val"].val())); // and there is a format helper CHECK(err.check_error_occurs([&]{ auto checku8 = ryml::fmt::overflow_checked(valu8); // need to declare the wrapper type before using it with >> t["val"] >> checku8; // this will cause an error })); CHECK(err.check_error_occurs([&]{ auto checki8 = ryml::fmt::overflow_checked(vali8); // need to declare the wrapper type before using it with >> t["val"] >> checki8; // this will cause an error })); } } //----------------------------------------------------------------------------- /** Shows how to deal with empty/null values. See also @ref * c4::yml::Tree::val_is_null */ void sample_empty_null_values() { // reading empty/null values - see also sample_formatting() ryml::Tree tree = ryml::parse_in_arena(R"( plain: squoted: '' dquoted: "" literal: | folded: > all_null: [~, null, Null, NULL] non_null: [nULL, non_null, non null, null it is not] )"); // first, remember that .has_val() is a structural predicate // indicating the node is a leaf, and not a container. CHECK(tree["plain"].has_val()); // has a val, even if it's empty! CHECK(tree["squoted"].has_val()); CHECK(tree["dquoted"].has_val()); CHECK(tree["literal"].has_val()); CHECK(tree["folded"].has_val()); CHECK( ! tree["all_null"].has_val()); CHECK( ! tree["non_null"].has_val()); // In essence, has_val() is the logical opposite of is_container() CHECK( ! tree["plain"].is_container()); CHECK( ! tree["squoted"].is_container()); CHECK( ! tree["dquoted"].is_container()); CHECK( ! tree["literal"].is_container()); CHECK( ! tree["folded"].is_container()); CHECK(tree["all_null"].is_container()); CHECK(tree["non_null"].is_container()); // // Right. How about the contents of each val? // // all of these scalars have zero-length: CHECK(tree["plain"].val().len == 0); CHECK(tree["squoted"].val().len == 0); CHECK(tree["dquoted"].val().len == 0); CHECK(tree["literal"].val().len == 0); CHECK(tree["folded"].val().len == 0); // but only the empty scalar has null string: CHECK(tree["plain"].val().str == nullptr); CHECK(tree["squoted"].val().str != nullptr); CHECK(tree["dquoted"].val().str != nullptr); CHECK(tree["literal"].val().str != nullptr); CHECK(tree["folded"].val().str != nullptr); // likewise, scalar comparison to nullptr has the same results: // (remember that .val() gives you the scalar value, node must // have a val, ie must be a leaf node, not a container) CHECK(tree["plain"].val() == nullptr); CHECK(tree["squoted"].val() != nullptr); CHECK(tree["dquoted"].val() != nullptr); CHECK(tree["literal"].val() != nullptr); CHECK(tree["folded"].val() != nullptr); // the tree and node classes provide the corresponding predicate // functions .key_is_null() and .val_is_null(). // (note that these functions have the same preconditions as .val(), // because they need get the val to look into its contents) CHECK(tree["plain"].val_is_null()); CHECK( ! tree["squoted"].val_is_null()); CHECK( ! tree["dquoted"].val_is_null()); CHECK( ! tree["literal"].val_is_null()); CHECK( ! tree["folded"].val_is_null()); // matching to null is case-sensitive. only the cases shown here // match to null: for(ryml::ConstNodeRef child : tree["all_null"].children()) { CHECK(child.val() != nullptr); // it is pointing at a string, so it is not nullptr! CHECK(child.val_is_null()); } for(ryml::ConstNodeRef child : tree["non_null"].children()) { CHECK(child.val() != nullptr); CHECK( ! child.val_is_null()); } // // // Because the meaning of null/~/empty will vary from application // to application, ryml makes no assumption on what should be // serialized as null. It leaves this decision to the user. But // it also provides the proper toolbox for the user to implement // its intended solution. // // writing/disambiguating null values: ryml::csubstr null = {}; ryml::csubstr nonnull = ""; ryml::csubstr strnull = "null"; ryml::csubstr tilde = "~"; CHECK(null .len == 0); CHECK(null .str == nullptr); CHECK(null == nullptr); CHECK(nonnull.len == 0); CHECK(nonnull.str != nullptr); CHECK(nonnull != nullptr); CHECK(strnull.len != 0); CHECK(strnull.str != nullptr); CHECK(strnull != nullptr); CHECK(tilde .len != 0); CHECK(tilde .str != nullptr); CHECK(tilde != nullptr); tree.clear(); tree.clear_arena(); tree.rootref() |= ryml::MAP; // serializes as an empty plain scalar: tree["empty_null"] << null; CHECK(tree.arena() == ""); // serializes as an empty quoted scalar: tree["empty_nonnull"] << nonnull; CHECK(tree.arena() == ""); // serializes as the normal 'null' string: tree["str_null"] << strnull; CHECK(tree.arena() == "null"); // serializes as the normal '~' string: tree["str_tilde"] << tilde; CHECK(tree.arena() == "null~"); // this is the resulting yaml: CHECK(ryml::emitrs_yaml(tree) == R"(empty_null: empty_nonnull: '' str_null: null str_tilde: ~ )"); // To enforce a particular concept of what is a null string, you // can use the appropriate condition based on pointer nulity or // other appropriate criteria. // // As an example, proper comparison to nullptr: auto null_if_nullptr = [](ryml::csubstr s) { return s.str == nullptr ? "null" : s; }; tree["empty_null"] << null_if_nullptr(null); tree["empty_nonnull"] << null_if_nullptr(nonnull); tree["str_null"] << null_if_nullptr(strnull); tree["str_tilde"] << null_if_nullptr(tilde); // this is the resulting yaml: CHECK(ryml::emitrs_yaml(tree) == R"(empty_null: null empty_nonnull: '' str_null: null str_tilde: ~ )"); // // As another example, nulity check based on the YAML nulity // predicate: auto null_if_predicate = [](ryml::csubstr s) { return ryml::scalar_is_null(s) ? "null" : s; }; tree["empty_null"] << null_if_predicate(null); tree["empty_nonnull"] << null_if_predicate(nonnull); tree["str_null"] << null_if_predicate(strnull); tree["str_tilde"] << null_if_predicate(tilde); // this is the resulting yaml: CHECK(ryml::emitrs_yaml(tree) == R"(empty_null: null empty_nonnull: '' str_null: null str_tilde: null )"); // // As another example, nulity check based on the YAML nulity // predicate, but returning "~" to simbolize nulity: auto tilde_if_predicate = [](ryml::csubstr s) { return ryml::scalar_is_null(s) ? "~" : s; }; tree["empty_null"] << tilde_if_predicate(null); tree["empty_nonnull"] << tilde_if_predicate(nonnull); tree["str_null"] << tilde_if_predicate(strnull); tree["str_tilde"] << tilde_if_predicate(tilde); // this is the resulting yaml: CHECK(ryml::emitrs_yaml(tree) == R"(empty_null: ~ empty_nonnull: '' str_null: ~ str_tilde: ~ )"); } //----------------------------------------------------------------------------- /** ryml provides facilities for formatting/deformatting (imported * from c4core into the ryml namespace). See @ref doc_format_utils * . These functions are very useful to serialize and deserialize * scalar types; see @ref doc_serialization . */ void sample_formatting() { // format(), format_sub(), formatrs(): format arguments { char buf_[256] = {}; ryml::substr buf = buf_; size_t size = ryml::format(buf, "a={} foo {} {} bar {}", 0.1, 10, 11, 12); CHECK(size == strlen("a=0.1 foo 10 11 bar 12")); CHECK(buf.first(size) == "a=0.1 foo 10 11 bar 12"); // it is safe to call on an empty buffer: // returns the size needed for the result, and no overflow occurs: size = ryml::format({} , "a={} foo {} {} bar {}", "this_is_a", 10, 11, 12); CHECK(size == ryml::format(buf, "a={} foo {} {} bar {}", "this_is_a", 10, 11, 12)); CHECK(size == strlen("a=this_is_a foo 10 11 bar 12")); // it is also safe to call on an insufficient buffer: char smallbuf[8] = {}; size = ryml::format(smallbuf, "{} is too large {}", "this", "for the buffer"); CHECK(size == strlen("this is too large for the buffer")); // ... and the result is truncated at the buffer size: CHECK(ryml::substr(smallbuf, sizeof(smallbuf)) == "this is\0"); // format_sub() directly returns the written string: ryml::csubstr result = ryml::format_sub(buf, "b={}, damn it.", 1); CHECK(result == "b=1, damn it."); CHECK(result.is_sub(buf)); // formatrs() means FORMAT & ReSize: // // Instead of a substr, it receives any owning linear char container // for which to_substr() is defined (using ADL). // has to_substr() definitions for std::string and // std::vector. // // formatrs() starts by calling format(), and if needed, resizes the container // and calls format() again. // // Note that unless the container is previously sized, this // may cause an allocation, which will make your code slower. // Make sure to call .reserve() on the container for real // production code. std::string sbuf; ryml::formatrs(&sbuf, "and c={} seems about right", 2); CHECK(sbuf == "and c=2 seems about right"); std::vector vbuf; // works with any linear char container ryml::formatrs(&vbuf, "and c={} seems about right", 2); CHECK(sbuf == "and c=2 seems about right"); // with formatrs() it is also possible to append: ryml::formatrs_append(&sbuf, ", and finally d={} - done", 3); CHECK(sbuf == "and c=2 seems about right, and finally d=3 - done"); } // unformat(): read arguments - opposite of format() { char buf_[256]; int a = 0, b = 1, c = 2; ryml::csubstr result = ryml::format_sub(buf_, "{} and {} and {}", a, b, c); CHECK(result == "0 and 1 and 2"); int aa = -1, bb = -2, cc = -3; size_t num_characters = ryml::unformat(result, "{} and {} and {}", aa, bb, cc); CHECK(num_characters != ryml::csubstr::npos); // if a conversion fails, returns ryml::csubstr::npos CHECK(num_characters == result.size()); CHECK(aa == a); CHECK(bb == b); CHECK(cc == c); result = ryml::format_sub(buf_, "{} and {} and {}", 10, 20, 30); CHECK(result == "10 and 20 and 30"); num_characters = ryml::unformat(result, "{} and {} and {}", aa, bb, cc); CHECK(num_characters != ryml::csubstr::npos); // if a conversion fails, returns ryml::csubstr::npos CHECK(num_characters == result.size()); CHECK(aa == 10); CHECK(bb == 20); CHECK(cc == 30); } // cat(), cat_sub(), catrs(): concatenate arguments { char buf_[256] = {}; ryml::substr buf = buf_; size_t size = ryml::cat(buf, "a=", 0.1, "foo", 10, 11, "bar", 12); CHECK(size == strlen("a=0.1foo1011bar12")); CHECK(buf.first(size) == "a=0.1foo1011bar12"); // it is safe to call on an empty buffer: // returns the size needed for the result, and no overflow occurs: CHECK(ryml::cat({}, "a=", 0) == 3); // it is also safe to call on an insufficient buffer: char smallbuf[8] = {}; size = ryml::cat(smallbuf, "this", " is too large ", "for the buffer"); CHECK(size == strlen("this is too large for the buffer")); // ... and the result is truncated at the buffer size: CHECK(ryml::substr(smallbuf, sizeof(smallbuf)) == "this is\0"); // cat_sub() directly returns the written string: ryml::csubstr result = ryml::cat_sub(buf, "b=", 1, ", damn it."); CHECK(result == "b=1, damn it."); CHECK(result.is_sub(buf)); // catrs() means CAT & ReSize: // // Instead of a substr, it receives any owning linear char container // for which to_substr() is defined (using ADL). // has to_substr() definitions for std::string and // std::vector. // // catrs() starts by calling cat(), and if needed, resizes the container // and calls cat() again. // // Note that unless the container is previously sized, this // may cause an allocation, which will make your code slower. // Make sure to call .reserve() on the container for real // production code. std::string sbuf; ryml::catrs(&sbuf, "and c=", 2, " seems about right"); CHECK(sbuf == "and c=2 seems about right"); std::vector vbuf; // works with any linear char container ryml::catrs(&vbuf, "and c=", 2, " seems about right"); CHECK(sbuf == "and c=2 seems about right"); // with catrs() it is also possible to append: ryml::catrs_append(&sbuf, ", and finally d=", 3, " - done"); CHECK(sbuf == "and c=2 seems about right, and finally d=3 - done"); } // uncat(): read arguments - opposite of cat() { char buf_[256]; int a = 0, b = 1, c = 2; ryml::csubstr result = ryml::cat_sub(buf_, a, ' ', b, ' ', c); CHECK(result == "0 1 2"); int aa = -1, bb = -2, cc = -3; char sep1 = 'a', sep2 = 'b'; size_t num_characters = ryml::uncat(result, aa, sep1, bb, sep2, cc); CHECK(num_characters == result.size()); CHECK(aa == a); CHECK(bb == b); CHECK(cc == c); CHECK(sep1 == ' '); CHECK(sep2 == ' '); result = ryml::cat_sub(buf_, 10, ' ', 20, ' ', 30); CHECK(result == "10 20 30"); num_characters = ryml::uncat(result, aa, sep1, bb, sep2, cc); CHECK(num_characters == result.size()); CHECK(aa == 10); CHECK(bb == 20); CHECK(cc == 30); CHECK(sep1 == ' '); CHECK(sep2 == ' '); } // catsep(), catsep_sub(), catseprs(): concatenate arguments, with a separator { char buf_[256] = {}; ryml::substr buf = buf_; // use ' ' as a separator size_t size = ryml::catsep(buf, ' ', "a=", 0, "b=", 1, "c=", 2, 45, 67); CHECK(buf.first(size) == "a= 0 b= 1 c= 2 45 67"); // any separator may be used // use " and " as a separator size = ryml::catsep(buf, " and ", "a=0", "b=1", "c=2", 45, 67); CHECK(buf.first(size) == "a=0 and b=1 and c=2 and 45 and 67"); // use " ... " as a separator size = ryml::catsep(buf, " ... ", "a=0", "b=1", "c=2", 45, 67); CHECK(buf.first(size) == "a=0 ... b=1 ... c=2 ... 45 ... 67"); // use '/' as a separator size = ryml::catsep(buf, '/', "a=", 0, "b=", 1, "c=", 2, 45, 67); CHECK(buf.first(size) == "a=/0/b=/1/c=/2/45/67"); // use 888 as a separator size = ryml::catsep(buf, 888, "a=0", "b=1", "c=2", 45, 67); CHECK(buf.first(size) == "a=0888b=1888c=28884588867"); // it is safe to call on an empty buffer: // returns the size needed for the result, and no overflow occurs: CHECK(size == ryml::catsep({}, 888, "a=0", "b=1", "c=2", 45, 67)); // it is also safe to call on an insufficient buffer: char smallbuf[8] = {}; CHECK(size == ryml::catsep(smallbuf, 888, "a=0", "b=1", "c=2", 45, 67)); CHECK(size == strlen("a=0888b=1888c=28884588867")); // ... and the result is truncated: CHECK(ryml::substr(smallbuf, sizeof(smallbuf)) == "a=0888b\0"); // catsep_sub() directly returns the written substr: ryml::csubstr result = ryml::catsep_sub(buf, " and ", "a=0", "b=1", "c=2", 45, 67); CHECK(result == "a=0 and b=1 and c=2 and 45 and 67"); CHECK(result.is_sub(buf)); // catseprs() means CATSEP & ReSize: // // Instead of a substr, it receives any owning linear char container // for which to_substr() is defined (using ADL). // has to_substr() definitions for std::string and // std::vector. // // catseprs() starts by calling catsep(), and if needed, resizes the container // and calls catsep() again. // // Note that unless the container is previously sized, this // may cause an allocation, which will make your code slower. // Make sure to call .reserve() on the container for real // production code. std::string sbuf; ryml::catseprs(&sbuf, " and ", "a=0", "b=1", "c=2", 45, 67); CHECK(sbuf == "a=0 and b=1 and c=2 and 45 and 67"); std::vector vbuf; // works with any linear char container ryml::catseprs(&vbuf, " and ", "a=0", "b=1", "c=2", 45, 67); CHECK(ryml::to_csubstr(vbuf) == "a=0 and b=1 and c=2 and 45 and 67"); // with catseprs() it is also possible to append: ryml::catseprs_append(&sbuf, " well ", " --- a=0", "b=11", "c=12", 145, 167); 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"); } // uncatsep(): read arguments with a separator - opposite of catsep() { char buf_[256] = {}; int a = 0, b = 1, c = 2; ryml::csubstr result = ryml::catsep_sub(buf_, ' ', a, b, c); CHECK(result == "0 1 2"); int aa = -1, bb = -2, cc = -3; char sep = 'b'; size_t num_characters = ryml::uncatsep(result, sep, aa, bb, cc); CHECK(num_characters == result.size()); CHECK(aa == a); CHECK(bb == b); CHECK(cc == c); CHECK(sep == ' '); sep = '_'; result = ryml::catsep_sub(buf_, ' ', 10, 20, 30); CHECK(result == "10 20 30"); num_characters = ryml::uncatsep(result, sep, aa, bb, cc); CHECK(num_characters == result.size()); CHECK(aa == 10); CHECK(bb == 20); CHECK(cc == 30); CHECK(sep == ' '); } // formatting individual arguments { using namespace ryml; // all the symbols below are in the ryml namespace. char buf_[256] = {}; // all the results below are written in this buffer substr buf = buf_; // -------------------------------------- // fmt::boolalpha(): format as true/false // -------------------------------------- // just as with std streams, printing a bool will output the integer value: CHECK("0" == cat_sub(buf, false)); CHECK("1" == cat_sub(buf, true)); // to force a "true"/"false", use fmt::boolalpha: CHECK("false" == cat_sub(buf, fmt::boolalpha(false))); CHECK("true" == cat_sub(buf, fmt::boolalpha(true))); // --------------------------------- // fmt::hex(): format as hexadecimal // --------------------------------- CHECK("0xff" == cat_sub(buf, fmt::hex(255))); CHECK("0x100" == cat_sub(buf, fmt::hex(256))); CHECK("-0xff" == cat_sub(buf, fmt::hex(-255))); CHECK("-0x100" == cat_sub(buf, fmt::hex(-256))); CHECK("3735928559" == cat_sub(buf, UINT32_C(0xdeadbeef))); CHECK("0xdeadbeef" == cat_sub(buf, fmt::hex(UINT32_C(0xdeadbeef)))); // ---------------------------- // fmt::bin(): format as binary // ---------------------------- CHECK("0b1000" == cat_sub(buf, fmt::bin(8))); CHECK("0b1001" == cat_sub(buf, fmt::bin(9))); CHECK("0b10001" == cat_sub(buf, fmt::bin(17))); CHECK("0b11001" == cat_sub(buf, fmt::bin(25))); CHECK("-0b1000" == cat_sub(buf, fmt::bin(-8))); CHECK("-0b1001" == cat_sub(buf, fmt::bin(-9))); CHECK("-0b10001" == cat_sub(buf, fmt::bin(-17))); CHECK("-0b11001" == cat_sub(buf, fmt::bin(-25))); // --------------------------- // fmt::bin(): format as octal // --------------------------- CHECK("0o77" == cat_sub(buf, fmt::oct(63))); CHECK("0o100" == cat_sub(buf, fmt::oct(64))); CHECK("0o377" == cat_sub(buf, fmt::oct(255))); CHECK("0o400" == cat_sub(buf, fmt::oct(256))); CHECK("0o1000" == cat_sub(buf, fmt::oct(512))); CHECK("-0o77" == cat_sub(buf, fmt::oct(-63))); CHECK("-0o100" == cat_sub(buf, fmt::oct(-64))); CHECK("-0o377" == cat_sub(buf, fmt::oct(-255))); CHECK("-0o400" == cat_sub(buf, fmt::oct(-256))); CHECK("-0o1000" == cat_sub(buf, fmt::oct(-512))); // --------------------------- // fmt::zpad(): pad with zeros // --------------------------- CHECK("000063" == cat_sub(buf, fmt::zpad(63, 6))); CHECK( "00063" == cat_sub(buf, fmt::zpad(63, 5))); CHECK( "0063" == cat_sub(buf, fmt::zpad(63, 4))); CHECK( "063" == cat_sub(buf, fmt::zpad(63, 3))); CHECK( "63" == cat_sub(buf, fmt::zpad(63, 2))); CHECK( "63" == cat_sub(buf, fmt::zpad(63, 1))); // will never trim the result CHECK( "63" == cat_sub(buf, fmt::zpad(63, 0))); // will never trim the result CHECK("0x00003f" == cat_sub(buf, fmt::zpad(fmt::hex(63), 6))); CHECK("0o000077" == cat_sub(buf, fmt::zpad(fmt::oct(63), 6))); CHECK("0b00011001" == cat_sub(buf, fmt::zpad(fmt::bin(25), 8))); // ------------------------------------------------ // fmt::left(): align left with a given field width // ------------------------------------------------ CHECK("63 " == cat_sub(buf, fmt::left(63, 6))); CHECK("63 " == cat_sub(buf, fmt::left(63, 5))); CHECK("63 " == cat_sub(buf, fmt::left(63, 4))); CHECK("63 " == cat_sub(buf, fmt::left(63, 3))); CHECK("63" == cat_sub(buf, fmt::left(63, 2))); CHECK("63" == cat_sub(buf, fmt::left(63, 1))); // will never trim the result CHECK("63" == cat_sub(buf, fmt::left(63, 0))); // will never trim the result // the fill character can be specified (defaults to ' '): CHECK("63----" == cat_sub(buf, fmt::left(63, 6, '-'))); CHECK("63++++" == cat_sub(buf, fmt::left(63, 6, '+'))); CHECK("63////" == cat_sub(buf, fmt::left(63, 6, '/'))); CHECK("630000" == cat_sub(buf, fmt::left(63, 6, '0'))); CHECK("63@@@@" == cat_sub(buf, fmt::left(63, 6, '@'))); CHECK("0x003f " == cat_sub(buf, fmt::left(fmt::zpad(fmt::hex(63), 4), 10))); // -------------------------------------------------- // fmt::right(): align right with a given field width // -------------------------------------------------- CHECK(" 63" == cat_sub(buf, fmt::right(63, 6))); CHECK(" 63" == cat_sub(buf, fmt::right(63, 5))); CHECK(" 63" == cat_sub(buf, fmt::right(63, 4))); CHECK(" 63" == cat_sub(buf, fmt::right(63, 3))); CHECK("63" == cat_sub(buf, fmt::right(63, 2))); CHECK("63" == cat_sub(buf, fmt::right(63, 1))); // will never trim the result CHECK("63" == cat_sub(buf, fmt::right(63, 0))); // will never trim the result // the fill character can be specified (defaults to ' '): CHECK("----63" == cat_sub(buf, fmt::right(63, 6, '-'))); CHECK("++++63" == cat_sub(buf, fmt::right(63, 6, '+'))); CHECK("////63" == cat_sub(buf, fmt::right(63, 6, '/'))); CHECK("000063" == cat_sub(buf, fmt::right(63, 6, '0'))); CHECK("@@@@63" == cat_sub(buf, fmt::right(63, 6, '@'))); CHECK(" 0x003f" == cat_sub(buf, fmt::right(fmt::zpad(fmt::hex(63), 4), 10))); // ------------------------------------------ // fmt::real(): format floating point numbers // ------------------------------------------ // see also sample_float_precision() CHECK("0" == cat_sub(buf, fmt::real(0.01f, 0))); CHECK("0.0" == cat_sub(buf, fmt::real(0.01f, 1))); CHECK("0.01" == cat_sub(buf, fmt::real(0.01f, 2))); CHECK("0.010" == cat_sub(buf, fmt::real(0.01f, 3))); CHECK("0.0100" == cat_sub(buf, fmt::real(0.01f, 4))); CHECK("0.01000" == cat_sub(buf, fmt::real(0.01f, 5))); CHECK("1" == cat_sub(buf, fmt::real(1.01f, 0))); CHECK("1.0" == cat_sub(buf, fmt::real(1.01f, 1))); CHECK("1.01" == cat_sub(buf, fmt::real(1.01f, 2))); CHECK("1.010" == cat_sub(buf, fmt::real(1.01f, 3))); CHECK("1.0100" == cat_sub(buf, fmt::real(1.01f, 4))); CHECK("1.01000" == cat_sub(buf, fmt::real(1.01f, 5))); CHECK("1" == cat_sub(buf, fmt::real(1.234234234, 0))); CHECK("1.2" == cat_sub(buf, fmt::real(1.234234234, 1))); CHECK("1.23" == cat_sub(buf, fmt::real(1.234234234, 2))); CHECK("1.234" == cat_sub(buf, fmt::real(1.234234234, 3))); CHECK("1.2342" == cat_sub(buf, fmt::real(1.234234234, 4))); CHECK("1.23423" == cat_sub(buf, fmt::real(1.234234234, 5))); CHECK("1000000.00000" == cat_sub(buf, fmt::real(1000000.000000000, 5))); CHECK("1234234.23423" == cat_sub(buf, fmt::real(1234234.234234234, 5))); // AKA %f CHECK("1000000.00000" == cat_sub(buf, fmt::real(1000000.000000000, 5, FTOA_FLOAT))); // AKA %f, same as above CHECK("1234234.23423" == cat_sub(buf, fmt::real(1234234.234234234, 5, FTOA_FLOAT))); // AKA %f CHECK("1234234.2342" == cat_sub(buf, fmt::real(1234234.234234234, 4, FTOA_FLOAT))); // AKA %f CHECK("1234234.234" == cat_sub(buf, fmt::real(1234234.234234234, 3, FTOA_FLOAT))); // AKA %f CHECK("1234234.23" == cat_sub(buf, fmt::real(1234234.234234234, 2, FTOA_FLOAT))); // AKA %f // AKA %e CHECK("1.00000e+06" == cat_sub(buf, fmt::real(1000000.000000000, 5, FTOA_SCIENT))); // AKA %e CHECK("1.23423e+06" == cat_sub(buf, fmt::real(1234234.234234234, 5, FTOA_SCIENT))); // AKA %e CHECK("1.2342e+06" == cat_sub(buf, fmt::real(1234234.234234234, 4, FTOA_SCIENT))); // AKA %e CHECK("1.234e+06" == cat_sub(buf, fmt::real(1234234.234234234, 3, FTOA_SCIENT))); // AKA %e CHECK("1.23e+06" == cat_sub(buf, fmt::real(1234234.234234234, 2, FTOA_SCIENT))); // AKA %e // AKA %g CHECK("1e+06" == cat_sub(buf, fmt::real(1000000.000000000, 5, FTOA_FLEX))); // AKA %g CHECK("1.2342e+06" == cat_sub(buf, fmt::real(1234234.234234234, 5, FTOA_FLEX))); // AKA %g CHECK("1.234e+06" == cat_sub(buf, fmt::real(1234234.234234234, 4, FTOA_FLEX))); // AKA %g CHECK("1.23e+06" == cat_sub(buf, fmt::real(1234234.234234234, 3, FTOA_FLEX))); // AKA %g CHECK("1.2e+06" == cat_sub(buf, fmt::real(1234234.234234234, 2, FTOA_FLEX))); // AKA %g // FTOA_HEXA: AKA %a (hexadecimal formatting of floats) CHECK("0x1.e8480p+19" == cat_sub(buf, fmt::real(1000000.000000000, 5, FTOA_HEXA))); // AKA %a CHECK("0x1.2d53ap+20" == cat_sub(buf, fmt::real(1234234.234234234, 5, FTOA_HEXA))); // AKA %a // -------------------------------------------------------------- // fmt::raw(): dump data in machine format (respecting alignment) // -------------------------------------------------------------- { C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH("-Wcast-align") // we're casting the values directly, so alignment is strictly respected. const uint32_t payload[] = {10, 20, 30, 40, UINT32_C(0xdeadbeef)}; // (package payload as a substr, for comparison only) csubstr expected = csubstr((const char *)payload, sizeof(payload)); csubstr actual = cat_sub(buf, fmt::raw(payload)); CHECK(!actual.overlaps(expected)); CHECK(0 == memcmp(expected.str, actual.str, expected.len)); // also possible with variables: for(const uint32_t value : payload) { // (package payload as a substr, for comparison only) expected = csubstr((const char *)&value, sizeof(value)); actual = cat_sub(buf, fmt::raw(value)); CHECK(actual.size() == sizeof(uint32_t)); CHECK(!actual.overlaps(expected)); CHECK(0 == memcmp(expected.str, actual.str, expected.len)); // with non-const data, fmt::craw() may be needed for disambiguation: actual = cat_sub(buf, fmt::craw(value)); CHECK(actual.size() == sizeof(uint32_t)); CHECK(!actual.overlaps(expected)); CHECK(0 == memcmp(expected.str, actual.str, expected.len)); // // read back: uint32_t result; auto reader = fmt::raw(result); // keeps a reference to result CHECK(&result == (uint32_t*)reader.buf); CHECK(reader.len == sizeof(uint32_t)); uncat(actual, reader); // and compare: // (vs2017/release/32bit does not reload result from cache, so force it) result = *(uint32_t*)reader.buf; CHECK(result == value); // roundtrip completed successfully } C4_SUPPRESS_WARNING_GCC_CLANG_POP } // ------------------------- // fmt::base64(): see below! // ------------------------- } } //----------------------------------------------------------------------------- /** demonstrates how to read and write base64-encoded blobs. @see @ref doc_base64 */ void sample_base64() { ryml::Tree tree; tree.rootref() |= ryml::MAP; struct text_and_base64 { ryml::csubstr text, base64; }; text_and_base64 cases[] = { {{"Love all, trust a few, do wrong to none."}, {"TG92ZSBhbGwsIHRydXN0IGEgZmV3LCBkbyB3cm9uZyB0byBub25lLg=="}}, {{"The fool doth think he is wise, but the wise man knows himself to be a fool."}, {"VGhlIGZvb2wgZG90aCB0aGluayBoZSBpcyB3aXNlLCBidXQgdGhlIHdpc2UgbWFuIGtub3dzIGhpbXNlbGYgdG8gYmUgYSBmb29sLg=="}}, {{"Brevity is the soul of wit."}, {"QnJldml0eSBpcyB0aGUgc291bCBvZiB3aXQu"}}, {{"All that glitters is not gold."}, {"QWxsIHRoYXQgZ2xpdHRlcnMgaXMgbm90IGdvbGQu"}}, }; // to encode base64 and write the result to val: for(text_and_base64 c : cases) { tree[c.text] << ryml::fmt::base64(c.text); CHECK(tree[c.text].val() == c.base64); } // to encode base64 and write the result to key: for(text_and_base64 c : cases) { tree.rootref().append_child() << ryml::key(ryml::fmt::base64(c.text)) << c.text; CHECK(tree[c.base64].val() == c.text); } CHECK(ryml::emitrs_yaml(tree) == R"('Love all, trust a few, do wrong to none.': TG92ZSBhbGwsIHRydXN0IGEgZmV3LCBkbyB3cm9uZyB0byBub25lLg== 'The fool doth think he is wise, but the wise man knows himself to be a fool.': VGhlIGZvb2wgZG90aCB0aGluayBoZSBpcyB3aXNlLCBidXQgdGhlIHdpc2UgbWFuIGtub3dzIGhpbXNlbGYgdG8gYmUgYSBmb29sLg== Brevity is the soul of wit.: QnJldml0eSBpcyB0aGUgc291bCBvZiB3aXQu All that glitters is not gold.: QWxsIHRoYXQgZ2xpdHRlcnMgaXMgbm90IGdvbGQu TG92ZSBhbGwsIHRydXN0IGEgZmV3LCBkbyB3cm9uZyB0byBub25lLg==: 'Love all, trust a few, do wrong to none.' VGhlIGZvb2wgZG90aCB0aGluayBoZSBpcyB3aXNlLCBidXQgdGhlIHdpc2UgbWFuIGtub3dzIGhpbXNlbGYgdG8gYmUgYSBmb29sLg==: 'The fool doth think he is wise, but the wise man knows himself to be a fool.' QnJldml0eSBpcyB0aGUgc291bCBvZiB3aXQu: Brevity is the soul of wit. QWxsIHRoYXQgZ2xpdHRlcnMgaXMgbm90IGdvbGQu: All that glitters is not gold. )"); char buf1_[128], buf2_[128]; ryml::substr buf1 = buf1_; // this is where we will write the result (using >>) ryml::substr buf2 = buf2_; // this is where we will write the result (using deserialize_val()/deserialize_key()) std::string result = {}; // show also how to decode to a std::string // to decode the val base64 and write the result to buf: for(const text_and_base64 c : cases) { // write the decoded result into the given buffer tree[c.text] >> ryml::fmt::base64(buf1); // cannot know the needed size size_t len = tree[c.text].deserialize_val(ryml::fmt::base64(buf2)); // returns the needed size CHECK(len <= buf1.len); CHECK(len <= buf2.len); CHECK(c.text.len == len); CHECK(buf1.first(len) == c.text); CHECK(buf2.first(len) == c.text); // // interop with std::string: using substr result.clear(); // this is not needed. We do it just to show that the first call can fail. len = tree[c.text].deserialize_val(ryml::fmt::base64(ryml::to_substr(result))); // returns the needed size if(len > result.size()) // the size was not enough; resize and call again { result.resize(len); len = tree[c.text].deserialize_val(ryml::fmt::base64(ryml::to_substr(result))); // returns the needed size } result.resize(len); // trim to the length of the decoded buffer CHECK(result == c.text); // // interop with std::string: using blob result.clear(); // this is not needed. We do it just to show that the first call can fail. ryml::blob strblob(&result[0], result.size()); CHECK(strblob.buf == result.data()); CHECK(strblob.len == result.size()); len = tree[c.text].deserialize_val(ryml::fmt::base64(strblob)); // returns the needed size if(len > result.size()) // the size was not enough; resize and call again { result.resize(len); strblob = {&result[0], result.size()}; CHECK(strblob.buf == result.data()); CHECK(strblob.len == result.size()); len = tree[c.text].deserialize_val(ryml::fmt::base64(strblob)); // returns the needed size } result.resize(len); // trim to the length of the decoded buffer CHECK(result == c.text); // // Note also these are just syntatic wrappers to simplify client code. // You can call into the lower level functions without much effort: result.clear(); // this is not needed. We do it just to show that the first call can fail. ryml::csubstr encoded = tree[c.text].val(); CHECK(encoded == c.base64); len = base64_decode(encoded, ryml::blob{&result[0], result.size()}); if(len > result.size()) // the size was not enough; resize and call again { result.resize(len); len = base64_decode(encoded, ryml::blob{&result[0], result.size()}); } result.resize(len); // trim to the length of the decoded buffer CHECK(result == c.text); } // to decode the key base64 and write the result to buf: for(const text_and_base64 c : cases) { // write the decoded result into the given buffer tree[c.base64] >> ryml::key(ryml::fmt::base64(buf1)); // cannot know the needed size size_t len = tree[c.base64].deserialize_key(ryml::fmt::base64(buf2)); // returns the needed size CHECK(len <= buf1.len); CHECK(len <= buf2.len); CHECK(c.text.len == len); CHECK(buf1.first(len) == c.text); CHECK(buf2.first(len) == c.text); // interop with std::string: using substr result.clear(); // this is not needed. We do it just to show that the first call can fail. len = tree[c.base64].deserialize_key(ryml::fmt::base64(ryml::to_substr(result))); // returns the needed size if(len > result.size()) // the size was not enough; resize and call again { result.resize(len); len = tree[c.base64].deserialize_key(ryml::fmt::base64(ryml::to_substr(result))); // returns the needed size } result.resize(len); // trim to the length of the decoded buffer CHECK(result == c.text); // // interop with std::string: using blob result.clear(); // this is not needed. We do it just to show that the first call can fail. ryml::blob strblob = {&result[0], result.size()}; CHECK(strblob.buf == result.data()); CHECK(strblob.len == result.size()); len = tree[c.base64].deserialize_key(ryml::fmt::base64(strblob)); // returns the needed size if(len > result.size()) // the size was not enough; resize and call again { result.resize(len); strblob = {&result[0], result.size()}; CHECK(strblob.buf == result.data()); CHECK(strblob.len == result.size()); len = tree[c.base64].deserialize_key(ryml::fmt::base64(strblob)); // returns the needed size } result.resize(len); // trim to the length of the decoded buffer CHECK(result == c.text); // // Note also these are just syntactic wrappers to simplify client code. // You can call into the lower level functions without much effort: result.clear(); // this is not needed. We do it just to show that the first call can fail. ryml::csubstr encoded = tree[c.base64].key(); CHECK(encoded == c.base64); len = base64_decode(encoded, ryml::blob{&result[0], result.size()}); if(len > result.size()) // the size was not enough; resize and call again { result.resize(len); len = base64_decode(encoded, ryml::blob{&result[0], result.size()}); } result.resize(len); // trim to the length of the decoded buffer CHECK(result == c.text); } // directly encode variables { const uint64_t valin = UINT64_C(0xdeadbeef); uint64_t valout = 0; tree["deadbeef"] << c4::fmt::base64(valin); // sometimes cbase64() is needed to avoid ambiguity size_t len = tree["deadbeef"].deserialize_val(ryml::fmt::base64(valout)); CHECK(len <= sizeof(valout)); CHECK(valout == UINT64_C(0xdeadbeef)); // base64 roundtrip is bit-accurate } // directly encode memory ranges { const uint32_t data_in[11] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xdeadbeef}; uint32_t data_out[11] = {}; CHECK(memcmp(data_in, data_out, sizeof(data_in)) != 0); // before the roundtrip tree["int_data"] << c4::fmt::base64(data_in); size_t len = tree["int_data"].deserialize_val(ryml::fmt::base64(data_out)); CHECK(len <= sizeof(data_out)); CHECK(memcmp(data_in, data_out, sizeof(data_in)) == 0); // after the roundtrip } } //----------------------------------------------------------------------------- // Serialization info /** @} */ // doc_quickstart /** @addtogroup doc_serialization * * @{ * * ## Fundamental types * * ryml provides serialization/deserialization utilities for all * fundamental data types in @ref doc_charconv . * * - See @ref sample_fundamental_types() for basic examples * of serialization of fundamental types. * - See @ref sample_empty_null_values() for different ways * to serialize and deserialize empty and null values/ * - When serializing floating point values in C++ earlier than * 17, be aware that there may be a truncation of the precision * with the default float/double implementations of @ref * doc_to_chars. To enforce a particular precision, use for * example @ref c4::fmt::real, or call directly @ref c4::ftoa() or * @ref c4::dtoa(), or any other method (remember that ryml only * stores the final string in the tree, so nothing prevents you from * creating it in whatever way is most suitable). See the relevant * sample: @ref sample_float_precision(). * - You can also serialize and deserialize base64: see @ref * doc_base64 and @ref sample_base64 * * To serialize/deserialize any non-fundamental type will require * that you instruct ryml on how to achieve this. That will differ * based on whether the type is scalar or container. * * * ## User scalar types * * See @ref doc_sample_scalar_types for serializing user scalar types * (ie leaf nodes in the YAML tree, containing a string * representation): * * - See examples on how to @ref doc_sample_to_chars_scalar * - See examples on how to @ref doc_sample_from_chars_scalar * - See the sample @ref sample_user_scalar_types * - See the sample @ref sample_formatting for examples * of functions from @ref doc_format_utils that will be very * helpful in implementing custom `to_chars()`/`from_chars()` * functions. * - See @ref doc_charconv for the implementations of * `to_chars()`/`from_chars()` for the fundamental types. * - See @ref doc_substr and @ref sample_substr() for the * many useful utilities in the substring class. * * * ## User container types * * - See @ref doc_sample_container_types for when the type is a * container (ie, a node which has children, which may themselves be * containers). * * - See the sample @ref sample_user_container_types * * - See the sample @ref sample_std_types, and also... * * * ## STL types * * ryml does not use any STL containers internally, but it can be * used to serialize and deserialize these containers. See @ref * sample_std_types() for an example. See the header @ref * ryml_std.hpp and also the headers it includes: * * - scalar types: * - for `std::string`: @ref ext/c4core/src/c4/std/string.hpp * - for `std::string_view`: @ref ext/c4core/src/c4/std/string_view.hpp * - for `std::vector`: @ref ext/c4core/src/c4/std/vector.hpp * - container types: * - for `std::vector`: @ref src/c4/yml/std/vector.hpp * - for `std::map`: @ref src/c4/yml/std/map.hpp * * @} * * @addtogroup doc_quickstart * @{ */ //----------------------------------------------------------------------------- // user scalar types: implemented in ryml through to_chars() + from_chars() /** @addtogroup doc_sample_helpers * @{ */ /** @defgroup doc_sample_scalar_types Serialize/deserialize scalar types * @{ */ template struct vec2 { T x, y; }; ///< example scalar type, serialized and deserialized template struct vec3 { T x, y, z; }; ///< example scalar type, serialized and deserialized template struct vec4 { T x, y, z, w; }; ///< example scalar type, serialized and deserialized template struct parse_only_vec2 { T x, y; }; ///< example scalar type, deserialized only template struct parse_only_vec3 { T x, y, z; }; ///< example scalar type, deserialized only template struct parse_only_vec4 { T x, y, z, w; }; ///< example scalar type, deserialized only template struct emit_only_vec2 { T x, y; }; ///< example scalar type, serialized only template struct emit_only_vec3 { T x, y, z; }; ///< example scalar type, serialized only template struct emit_only_vec4 { T x, y, z, w; }; ///< example scalar type, serialized only /** @defgroup doc_sample_to_chars_scalar Define to_chars to write scalar types * * @brief To serialize user scalar types, implement the appropriate * function to_chars (see also @ref doc_to_chars): * * ```cpp * // any of these can be used: * size_t to_chars(substr buf, T const& v); * size_t to_chars(substr buf, T v); // this also works, and is good when the type is small * ``` * * See the sample @ref sample_user_scalar_types() for an example usage. * * Your implementation of to_chars must format v to the given string * view + return the number of characters written into it. The view * size (buf.len) must be strictly respected. Return the number of * characters that need to be written for the value to be completely * serialized in the string. So if the return value is larger than * buf.len, ryml will know that the buffer resize the buffer and call * this again with a larger buffer of the correct size. * * In your implementation, you may be interested in using the * formatting facilities in @ref doc_format_utils and @ref doc_charconv; * refer to their documentation for further details. But this is not * mandatory, and anything can be used, provided that the implemented * `to_chars()` fulfills its contract, described above. * * @warning Because of [C++'s ADL * rules](http://en.cppreference.com/w/cpp/language/adl), **it is * required to overload these functions in the namespace of the type** * you're serializing (or in the c4 namespace, or in the c4::yml * namespace). [Here's an example of an issue where failing to do this * was causing problems in some * platforms](https://github.com/biojppm/rapidyaml/issues/424) * * @note Please take note of the following pitfall when using * serialization functions: you may have to include the header with * your `to_chars()` implementation before any other headers that use * functions from it. See the include order at the top of this source * file. This constraint also applies to the conversion functions for * your types; just like with the STL's headers, they should be * included prior to ryml's headers. Lately, some effort was directed * to provide forward declarations to alleviate this problem, but it * may still occur. * * @see string.hpp * @see string_view.hpp * @{ */ template size_t to_chars(ryml::substr buf, vec2 v) { return ryml::format(buf, "({},{})", v.x, v.y); } template size_t to_chars(ryml::substr buf, vec3 v) { return ryml::format(buf, "({},{},{})", v.x, v.y, v.z); } template size_t to_chars(ryml::substr buf, vec4 v) { return ryml::format(buf, "({},{},{},{})", v.x, v.y, v.z, v.w); } template size_t to_chars(ryml::substr buf, emit_only_vec2 v) { return ryml::format(buf, "({},{})", v.x, v.y); } template size_t to_chars(ryml::substr buf, emit_only_vec3 v) { return ryml::format(buf, "({},{},{})", v.x, v.y, v.z); } template size_t to_chars(ryml::substr buf, emit_only_vec4 v) { return ryml::format(buf, "({},{},{},{})", v.x, v.y, v.z, v.w); } /** @} */ /** @defgroup doc_sample_from_chars_scalar Define from_chars to read scalar types * * @brief To deserialize user scalar types, implement the * function `bool from_chars(csubstr buf, T *val)`; see @ref * doc_from_chars. * * The implementation of from_chars must never read beyond the limit * of the given buffer, and must return true/false to indicate * success/failure in the deserialization. On failure, it is up to you * whether the value is left unchanged; ryml itself does not care * about the value when the deserialization failed. * * In your implementation, you may be interested in using the * reading facilities in @ref doc_format_utils and @ref doc_charconv; * refer to their documentation for further details. But this is not * mandatory, and anything can be used, provided that the implemented * from_chars fulfills its contract, described above. * * @warning Because of [C++'s ADL * rules](http://en.cppreference.com/w/cpp/language/adl), **it is * required to overload these functions in the namespace of the type** * you're serializing (or in the c4 namespace, or in the c4::yml * namespace). [Here's an example of an issue where failing to do this * was causing problems in some * platforms](https://github.com/biojppm/rapidyaml/issues/424) * * @note Please take note of the following pitfall when using * serialization functions: you may have to include the header with * your `from_chars()` implementation before any other headers that use * functions from it. See the include order at the top of this source * file. This constraint also applies to the conversion functions for * your types; just like with the STL's headers, they should be * included prior to ryml's headers. Lately, some effort was directed * to provide forward declarations to alleviate this problem, but it * may still occur. * * @{ */ template bool from_chars(ryml::csubstr buf, vec2 *v) { size_t ret = ryml::unformat(buf, "({},{})", v->x, v->y); return ret != ryml::yml::npos; } template bool from_chars(ryml::csubstr buf, vec3 *v) { size_t ret = ryml::unformat(buf, "({},{},{})", v->x, v->y, v->z); return ret != ryml::yml::npos; } template bool from_chars(ryml::csubstr buf, vec4 *v) { size_t ret = ryml::unformat(buf, "({},{},{},{})", v->x, v->y, v->z, v->w); return ret != ryml::yml::npos; } template bool from_chars(ryml::csubstr buf, parse_only_vec2 *v) { size_t ret = ryml::unformat(buf, "({},{})", v->x, v->y); return ret != ryml::yml::npos; } template bool from_chars(ryml::csubstr buf, parse_only_vec3 *v) { size_t ret = ryml::unformat(buf, "({},{},{})", v->x, v->y, v->z); return ret != ryml::yml::npos; } template bool from_chars(ryml::csubstr buf, parse_only_vec4 *v) { size_t ret = ryml::unformat(buf, "({},{},{},{})", v->x, v->y, v->z, v->w); return ret != ryml::yml::npos; } /** @} */ // doc_sample_from_chars_scalar /** @} */ // doc_sample_scalar_types /** @} */ // doc_sample_helpers /** to add scalar types (ie leaf types converting to/from string), * define the functions above for those types. See @ref * doc_sample_scalar_types. */ void sample_user_scalar_types() { ryml::Tree t; auto r = t.rootref(); r |= ryml::MAP; vec2 v2in{10, 11}; vec2 v2out{1, 2}; r["v2"] << v2in; // serializes to the tree's arena, and then sets the keyval r["v2"] >> v2out; CHECK(v2in.x == v2out.x); CHECK(v2in.y == v2out.y); vec3 v3in{100, 101, 102}; vec3 v3out{1, 2, 3}; r["v3"] << v3in; // serializes to the tree's arena, and then sets the keyval r["v3"] >> v3out; CHECK(v3in.x == v3out.x); CHECK(v3in.y == v3out.y); CHECK(v3in.z == v3out.z); vec4 v4in{1000, 1001, 1002, 1003}; vec4 v4out{1, 2, 3, 4}; r["v4"] << v4in; // serializes to the tree's arena, and then sets the keyval r["v4"] >> v4out; CHECK(v4in.x == v4out.x); CHECK(v4in.y == v4out.y); CHECK(v4in.z == v4out.z); CHECK(v4in.w == v4out.w); CHECK(ryml::emitrs_yaml(t) == R"(v2: '(10,11)' v3: '(100,101,102)' v4: '(1000,1001,1002,1003)' )"); // note that only the used functions are needed: // - if a type is only parsed, then only from_chars() is needed // - if a type is only emitted, then only to_chars() is needed emit_only_vec2 eov2in{20, 21}; // only has to_chars() parse_only_vec2 pov2out{1, 2}; // only has from_chars() r["v2"] << eov2in; // serializes to the tree's arena, and then sets the keyval r["v2"] >> pov2out; CHECK(eov2in.x == pov2out.x); CHECK(eov2in.y == pov2out.y); emit_only_vec3 eov3in{30, 31, 32}; // only has to_chars() parse_only_vec3 pov3out{1, 2, 3}; // only has from_chars() r["v3"] << eov3in; // serializes to the tree's arena, and then sets the keyval r["v3"] >> pov3out; CHECK(eov3in.x == pov3out.x); CHECK(eov3in.y == pov3out.y); CHECK(eov3in.z == pov3out.z); emit_only_vec4 eov4in{40, 41, 42, 43}; // only has to_chars() parse_only_vec4 pov4out{1, 2, 3, 4}; // only has from_chars() r["v4"] << eov4in; // serializes to the tree's arena, and then sets the keyval r["v4"] >> pov4out; CHECK(eov4in.x == pov4out.x); CHECK(eov4in.y == pov4out.y); CHECK(eov4in.z == pov4out.z); CHECK(ryml::emitrs_yaml(t) == R"(v2: '(20,21)' v3: '(30,31,32)' v4: '(40,41,42,43)' )"); } //----------------------------------------------------------------------------- // user container types: implemented in ryml through write() + read() /** @addtogroup doc_sample_helpers * @{ */ /** @defgroup doc_sample_container_types Serialize/deserialize container types * * To serialize/deserialize container types to a tree, implement the * appropriate functions: * * ```cpp * void write(ryml::NodeRef *n, T const& seq); * bool read(ryml::ConstNodeRef const& n, T *seq); * ``` * * @warning Because of [C++'s ADL * rules](http://en.cppreference.com/w/cpp/language/adl), **it is * required to overload these functions in the namespace of the type** * you're serializing (or in the c4 namespace, or in the c4::yml * namespace). [Here's an example of an issue where failing to do this * was causing problems in some * platforms](https://github.com/biojppm/rapidyaml/issues/424) * * @note Please take note of the following pitfall when using * serialization functions: you may have to include the header with * your `write()` or `read()` implementation before any other headers * that use functions from it. See the include order at the top of * this source file. This constraint also applies to the conversion * functions for your types; just like with the STL's headers, they * should be included prior to ryml's headers. Lately, some effort was * directed to provide forward declarations to alleviate this problem, * but it may still occur. * * @see sample_container_types * @see sample_std_types * * @{ */ /** example user container type: seq-like */ template struct my_seq_type { std::vector seq_member; }; /** example user container type: map-like */ template struct my_map_type { std::map map_member; }; /** example user container type with nested container members. * notice all the members have user-defined serialization methods. */ struct my_type { // these are leaf nodes: vec2 v2; vec3 v3; vec4 v4; // these are container nodes: my_seq_type seq; my_map_type map; }; template void write(ryml::NodeRef *n, my_seq_type const& seq) { *n |= ryml::SEQ; for(auto const& v : seq.seq_member) n->append_child() << v; } template void write(ryml::NodeRef *n, my_map_type const& map) { *n |= ryml::MAP; for(auto const& v : map.map_member) n->append_child() << ryml::key(v.first) << v.second; } void write(ryml::NodeRef *n, my_type const& val) { *n |= ryml::MAP; // these are leaf nodes: n->append_child() << ryml::key("v2") << val.v2; n->append_child() << ryml::key("v3") << val.v3; n->append_child() << ryml::key("v4") << val.v4; // these are container nodes: n->append_child() << ryml::key("seq") << val.seq; n->append_child() << ryml::key("map") << val.map; } template bool read(ryml::ConstNodeRef const& n, my_seq_type *seq) { seq->seq_member.resize(static_cast(n.num_children())); // num_children() is O(N) size_t pos = 0; for(auto const ch : n.children()) ch >> seq->seq_member[pos++]; return true; } template bool read(ryml::ConstNodeRef const& n, my_map_type *map) { K k{}; V v{}; for(auto const ch : n) { ch >> c4::yml::key(k) >> v; map->map_member.emplace(std::make_pair(std::move(k), std::move(v))); } return true; } bool read(ryml::ConstNodeRef const& n, my_type *val) { // these are leaf nodes: n["v2"] >> val->v2; n["v3"] >> val->v3; n["v4"] >> val->v4; // these are container nodes: n["seq"] >> val->seq; n["map"] >> val->map; return true; } /** @} */ // doc_sample_container_types /** @} */ // sample_helpers /** shows how to serialize/deserialize container types. * @see doc_sample_container_types * @see sample_std_types * */ void sample_user_container_types() { my_type mt_in{ {20, 21}, {30, 31, 32}, {40, 41, 42, 43}, {{101, 102, 103, 104, 105, 106, 107}}, {{{1001, 2001}, {1002, 2002}, {1003, 2003}}}, }; my_type mt_out; ryml::Tree t; t.rootref() << mt_in; // read from this t.crootref() >> mt_out; // assign here CHECK(mt_out.v2.x == mt_in.v2.x); CHECK(mt_out.v2.y == mt_in.v2.y); CHECK(mt_out.v3.x == mt_in.v3.x); CHECK(mt_out.v3.y == mt_in.v3.y); CHECK(mt_out.v3.z == mt_in.v3.z); CHECK(mt_out.v4.x == mt_in.v4.x); CHECK(mt_out.v4.y == mt_in.v4.y); CHECK(mt_out.v4.z == mt_in.v4.z); CHECK(mt_out.v4.w == mt_in.v4.w); CHECK(mt_in.seq.seq_member.size() > 0); CHECK(mt_out.seq.seq_member.size() == mt_in.seq.seq_member.size()); for(size_t i = 0; i < mt_in.seq.seq_member.size(); ++i) { CHECK(mt_out.seq.seq_member[i] == mt_in.seq.seq_member[i]); } CHECK(mt_in.map.map_member.size() > 0); CHECK(mt_out.map.map_member.size() == mt_in.map.map_member.size()); for(auto const& kv : mt_in.map.map_member) { CHECK(mt_out.map.map_member.find(kv.first) != mt_out.map.map_member.end()); CHECK(mt_out.map.map_member[kv.first] == kv.second); } CHECK(ryml::emitrs_yaml(t) == R"(v2: '(20,21)' v3: '(30,31,32)' v4: '(40,41,42,43)' seq: - 101 - 102 - 103 - 104 - 105 - 106 - 107 map: 1001: 2001 1002: 2002 1003: 2003 )"); } //----------------------------------------------------------------------------- /** demonstrates usage with the std implementations provided by ryml in the ryml_std.hpp header @see @ref doc_sample_container_types @see also the STL section in @ref doc_serialization */ void sample_std_types() { std::string yml_std_string = R"(- v2: '(20,21)' v3: '(30,31,32)' v4: '(40,41,42,43)' seq: - 101 - 102 - 103 - 104 - 105 - 106 - 107 map: 1001: 2001 1002: 2002 1003: 2003 - v2: '(120,121)' v3: '(130,131,132)' v4: '(140,141,142,143)' seq: - 1101 - 1102 - 1103 - 1104 - 1105 - 1106 - 1107 map: 11001: 12001 11002: 12002 11003: 12003 - v2: '(220,221)' v3: '(230,231,232)' v4: '(240,241,242,243)' seq: - 2101 - 2102 - 2103 - 2104 - 2105 - 2106 - 2107 map: 21001: 22001 21002: 22002 21003: 22003 )"; // parse in-place using the std::string above ryml::Tree tree = ryml::parse_in_place(ryml::to_substr(yml_std_string)); // my_type is a container-of-containers type. see above its // definition implementation for ryml. std::vector vmt; tree.rootref() >> vmt; CHECK(vmt.size() == 3); ryml::Tree tree_out; tree_out.rootref() << vmt; CHECK(ryml::emitrs_yaml(tree_out) == yml_std_string); } //----------------------------------------------------------------------------- /** control precision of serialized floats */ void sample_float_precision() { std::vector reference{1.23234412342131234, 2.12323123143434237, 3.67847983572591234}; // A safe precision for comparing doubles. May vary depending on // compiler flags. Double goes to about 15 digits, so 14 should be // safe enough for this test to succeed. const double precision_safe = 1.e-14; const size_t num_digits_safe = 14; const size_t num_digits_original = 17; auto get_num_digits = [](ryml::csubstr number){ return number.sub(2).len; }; // // no significant precision is lost when reading // floating point numbers: { ryml::Tree tree = ryml::parse_in_arena(R"([1.23234412342131234, 2.12323123143434237, 3.67847983572591234])"); std::vector output; tree.rootref() >> output; CHECK(output.size() == reference.size()); for(size_t i = 0; i < reference.size(); ++i) { CHECK(get_num_digits(tree[(ryml::id_type)i].val()) == num_digits_original); CHECK(fabs(output[i] - reference[i]) < precision_safe); } } // // However, depending on the compilation settings, there may be a // significant precision loss when serializing with the default // approach, operator<<(double): { ryml::Tree serialized; serialized.rootref() << reference; std::cout << serialized; // Without std::to_chars() there is a loss of precision: #if (!C4CORE_HAVE_STD_TOCHARS) // This macro is defined when std::to_chars() is available. CHECK(ryml::emitrs_yaml(serialized) == R"(- 1.23234 - 2.12323 - 3.67848 )" || (bool)"this is indicative; the exact results will vary from platform to platform."); C4_UNUSED(num_digits_safe); #else // ... but when using C++17 and above, the results are eminently equal: CHECK((ryml::emitrs_yaml(serialized) == R"(- 1.2323441234213124 - 2.1232312314343424 - 3.6784798357259123 )") || (bool)"this is indicative; the exact results will vary from platform to platform."); size_t pos = 0; for(ryml::ConstNodeRef child : serialized.rootref().children()) { CHECK(get_num_digits(child.val()) >= num_digits_safe); double out = {}; child >> out; CHECK(fabs(out - reference[pos++]) < precision_safe); } #endif } // // The difference is explained by the availability of // fastfloat::from_chars(), std::from_chars() and std::to_chars(). // // ryml prefers the fastfloat::from_chars() version. Unfortunately // fastfloat does not have to_chars() (see // https://github.com/fastfloat/fast_float/issues/23). // // When C++17 is used, ryml uses std::to_chars(), which produces // good defaults. // // However, with earlier standards, or in some library // implementations, there's only snprintf() available. Every other // std library function will either disrespect the string limits, // or more precisely, accept no string size limits. So the // implementation of c4core (which ryml uses) falls back to // snprintf("%g"), and that picks by default a (low) number of // digits. // // But all is not lost for C++11/C++14 users! // // To force a particular precision when serializing, you can use // c4::fmt::real() (brought into the ryml:: namespace). Or you can // serialize the number yourself! The small downside is that you // have to build the container. // // First a function to check the result: auto check_precision = [&](ryml::Tree const& serialized){ std::cout << serialized; // now it works! CHECK((ryml::emitrs_yaml(serialized) == R"(- 1.23234412342131239 - 2.12323123143434245 - 3.67847983572591231 )") || (bool)"this is indicative; the exact results will vary from platform to platform."); size_t pos = 0; for(ryml::ConstNodeRef child : serialized.rootref().children()) { CHECK(get_num_digits(child.val()) == num_digits_original); double out = {}; child >> out; CHECK(fabs(out - reference[pos++]) < precision_safe); } }; // // Serialization example using fmt::real() { ryml::Tree serialized; ryml::NodeRef root = serialized.rootref(); root |= ryml::SEQ; for(const double v : reference) root.append_child() << ryml::fmt::real(v, num_digits_original, ryml::FTOA_FLOAT); check_precision(serialized); // OK - now within bounds! } // // Serialization example using snprintf { ryml::Tree serialized; ryml::NodeRef root = serialized.rootref(); root |= ryml::SEQ; char tmp[64]; for(const double v : reference) { // reuse a buffer to serialize. // add 1 to the significant digits because the %g // specifier counts the integral digits. (void)snprintf(tmp, sizeof(tmp), "%.18g", v); // copy the serialized string to the tree (operator<< // copies to the arena, operator= just assigns the string // pointer and would be wrong in this case): root.append_child() << ryml::to_csubstr((const char*)tmp); } check_precision(serialized); // OK - now within bounds! } } //----------------------------------------------------------------------------- /** demonstrates how to emit to a linear container of char */ void sample_emit_to_container() { // it is possible to emit to any linear container of char. ryml::csubstr ymla = "- 1\n- 2\n"; ryml::csubstr ymlb = R"(- a - b - x0: 1 x1: 2 - champagne: Dom Perignon coffee: Arabica more: vinho verde: Soalheiro vinho tinto: Redoma 2017 beer: - Rochefort 10 - Busch - Leffe Rituel - foo - bar - baz - bat )"; const ryml::Tree treea = ryml::parse_in_arena(ymla); const ryml::Tree treeb = ryml::parse_in_arena(ymlb); // eg, std::vector { // do a blank call on an empty buffer to find the required size. // no overflow will occur, and returns a substr with the size // required to output ryml::csubstr output = ryml::emit_yaml(treea, treea.root_id(), ryml::substr{}, /*error_on_excess*/false); CHECK(output.str == nullptr); CHECK(output.len > 0); size_t num_needed_chars = output.len; std::vector buf(num_needed_chars); // now try again with the proper buffer output = ryml::emit_yaml(treea, treea.root_id(), ryml::to_substr(buf), /*error_on_excess*/true); CHECK(output == ymla); // it is possible to reuse the buffer and grow it as needed. // first do a blank run to find the size: output = ryml::emit_yaml(treeb, treeb.root_id(), ryml::substr{}, /*error_on_excess*/false); CHECK(output.str == nullptr); CHECK(output.len > 0); CHECK(output.len == ymlb.len); num_needed_chars = output.len; buf.resize(num_needed_chars); // now try again with the proper buffer output = ryml::emit_yaml(treeb, treeb.root_id(), ryml::to_substr(buf), /*error_on_excess*/true); CHECK(output == ymlb); // there is a convenience wrapper performing the same as above: // provided to_substr() is defined for that container. output = ryml::emitrs_yaml(treeb, &buf); CHECK(output == ymlb); // or you can just output a new container: // provided to_substr() is defined for that container. std::vector another = ryml::emitrs_yaml>(treeb); CHECK(ryml::to_csubstr(another) == ymlb); // you can also emit nested nodes: another = ryml::emitrs_yaml>(treeb[3][2]); CHECK(ryml::to_csubstr(another) == R"(more: vinho verde: Soalheiro vinho tinto: Redoma 2017 )"); } // eg, std::string. notice this is the same code as above { // do a blank call on an empty buffer to find the required size. // no overflow will occur, and returns a substr with the size // required to output ryml::csubstr output = ryml::emit_yaml(treea, treea.root_id(), ryml::substr{}, /*error_on_excess*/false); CHECK(output.str == nullptr); CHECK(output.len > 0); size_t num_needed_chars = output.len; std::string buf; buf.resize(num_needed_chars); // now try again with the proper buffer output = ryml::emit_yaml(treea, treea.root_id(), ryml::to_substr(buf), /*error_on_excess*/true); CHECK(output == ymla); // it is possible to reuse the buffer and grow it as needed. // first do a blank run to find the size: output = ryml::emit_yaml(treeb, treeb.root_id(), ryml::substr{}, /*error_on_excess*/false); CHECK(output.str == nullptr); CHECK(output.len > 0); CHECK(output.len == ymlb.len); num_needed_chars = output.len; buf.resize(num_needed_chars); // now try again with the proper buffer output = ryml::emit_yaml(treeb, treeb.root_id(), ryml::to_substr(buf), /*error_on_excess*/true); CHECK(output == ymlb); // there is a convenience wrapper performing the above instructions: // provided to_substr() is defined for that container output = ryml::emitrs_yaml(treeb, &buf); CHECK(output == ymlb); // or you can just output a new container: // provided to_substr() is defined for that container. std::string another = ryml::emitrs_yaml(treeb); CHECK(ryml::to_csubstr(another) == ymlb); // you can also emit nested nodes: another = ryml::emitrs_yaml(treeb[3][2]); CHECK(ryml::to_csubstr(another) == R"(more: vinho verde: Soalheiro vinho tinto: Redoma 2017 )"); } } //----------------------------------------------------------------------------- /** demonstrates how to emit to a stream-like structure */ void sample_emit_to_stream() { ryml::csubstr ymlb = R"(- a - b - x0: 1 x1: 2 - champagne: Dom Perignon coffee: Arabica more: vinho verde: Soalheiro vinho tinto: Redoma 2017 beer: - Rochefort 10 - Busch - Leffe Rituel - foo - bar - baz - bat )"; const ryml::Tree tree = ryml::parse_in_arena(ymlb); std::string s; // emit a full tree { std::stringstream ss; ss << tree; // works with any stream having .operator<<() and .write() s = ss.str(); CHECK(ryml::to_csubstr(s) == ymlb); } // emit a full tree as json { std::stringstream ss; ss << ryml::as_json(tree); // works with any stream having .operator<<() and .write() s = ss.str(); CHECK(ryml::to_csubstr(s) == R"([ "a", "b", { "x0": 1, "x1": 2 }, { "champagne": "Dom Perignon", "coffee": "Arabica", "more": { "vinho verde": "Soalheiro", "vinho tinto": "Redoma 2017" }, "beer": [ "Rochefort 10", "Busch", "Leffe Rituel" ] }, "foo", "bar", "baz", "bat" ] )"); } // emit a nested node { std::stringstream ss; ss << tree[3][2]; // works with any stream having .operator<<() and .write() s = ss.str(); CHECK(ryml::to_csubstr(s) == R"(more: vinho verde: Soalheiro vinho tinto: Redoma 2017 )"); } // emit a nested node as json { std::stringstream ss; ss << ryml::as_json(tree[3][2]); // works with any stream having .operator<<() and .write() s = ss.str(); CHECK(ryml::to_csubstr(s) == R"("more": { "vinho verde": "Soalheiro", "vinho tinto": "Redoma 2017" } )"); } } //----------------------------------------------------------------------------- /** demonstrates how to emit to a FILE* */ void sample_emit_to_file() { ryml::csubstr yml = R"(- a - b - x0: 1 x1: 2 - champagne: Dom Perignon coffee: Arabica more: vinho verde: Soalheiro vinho tinto: Redoma 2017 beer: - Rochefort 10 - Busch - Leffe Rituel - foo - bar - baz - bat )"; const ryml::Tree tree = ryml::parse_in_arena(yml); // this is emitting to stdout, but of course you can pass in any // FILE* obtained from fopen() size_t len = ryml::emit_yaml(tree, tree.root_id(), stdout); // the return value is the number of characters that were written // to the file CHECK(len == yml.len); } //----------------------------------------------------------------------------- /** just like parsing into a nested node, you can also emit from a nested node. */ void sample_emit_nested_node() { const ryml::Tree tree = ryml::parse_in_arena(R"(- a - b - x0: 1 x1: 2 - champagne: Dom Perignon coffee: Arabica more: vinho verde: Soalheiro vinho tinto: Redoma 2017 beer: - Rochefort 10 - Busch - Leffe Rituel - - and so - many other - wonderful beers - more - seq - members - here )"); CHECK(ryml::emitrs_yaml(tree[3]["beer"]) == R"(beer: - Rochefort 10 - Busch - Leffe Rituel - - and so - many other - wonderful beers )"); CHECK(ryml::emitrs_yaml(tree[3]["beer"][0]) == "Rochefort 10"); CHECK(ryml::emitrs_yaml(tree[3]["beer"][3]) == R"(- and so - many other - wonderful beers )"); } //----------------------------------------------------------------------------- /** [experimental] query/set/modify node style to control * formatting of emitted YAML code. */ void sample_style() { // we will be using this helper throughout this function auto tostr = [](ryml::ConstNodeRef n) { return ryml::emitrs_yaml(n); }; // let's parse this yaml: ryml::csubstr yaml = R"(block map: block key: block val block seq: - block val 1 - block val 2 - 'quoted' flow map, singleline: {flow key: flow val} flow seq, singleline: [flow val,flow val] flow map, multiline: { flow key: flow val } flow seq, multiline: [ flow val, flow val ] )"; ryml::Tree tree = ryml::parse_in_arena(yaml); // while parsing, ryml marks parsed nodes with their original style: CHECK(tree.rootref().is_block()); CHECK(tree["block map"].is_key_plain()); CHECK(tree["block seq"].is_key_plain()); CHECK(tree["flow map, singleline"].is_key_plain()); CHECK(tree["flow seq, singleline"].is_key_plain()); CHECK(tree["flow map, multiline"].is_key_plain()); CHECK(tree["flow seq, multiline"].is_key_plain()); CHECK(tree["block map"].is_block()); CHECK(tree["block seq"].is_block()); // flow is either singleline (FLOW_SL) or multiline (FLOW_ML) CHECK(tree["flow map, singleline"].is_flow_sl()); CHECK(tree["flow seq, singleline"].is_flow_sl()); CHECK(tree["flow map, multiline"].is_flow_ml()); CHECK(tree["flow seq, multiline"].is_flow_ml()); // is_flow() is equivalent to (is_flow_sl() || is_flow_ml()) CHECK(tree["flow map, singleline"].is_flow()); CHECK(tree["flow seq, singleline"].is_flow()); CHECK(tree["flow map, multiline"].is_flow()); CHECK(tree["flow seq, multiline"].is_flow()); // // since the tree nodes are marked with their original parsed // style, emitting the parsed tree will preserve the original // style (minus whitespace): // CHECK(tostr(tree) == yaml); // same as before! // // you can set/modify the style programatically! // // here are more examples. // { ryml::NodeRef n = tree["block map"]; // Let's look at one node // It looks like this originally: CHECK(tostr(n) == "block map:\n block key: block val\n"); // let's modify its style: n.set_key_style(ryml::KEY_SQUO); // scalar style: to single-quoted scalar n.set_container_style(ryml::FLOW_SL); // container style: to flow singleline // now it looks like this: CHECK(tostr(n) == "'block map': {block key: block val}\n"); } // next example { ryml::NodeRef n = tree["block seq"]; CHECK(tostr(n) == "block seq:\n - block val 1\n - block val 2\n - 'quoted'\n"); n.set_key_style(ryml::KEY_DQUO); // scalar style: to double-quoted scalar n.set_container_style(ryml::FLOW_ML); // container style: to flow multiline n[2].set_val_style(ryml::VAL_PLAIN); // scalar style: to plain CHECK(tostr(n) == "\"block seq\": [\n block val 1,\n block val 2,\n quoted\n ]\n"); } // next example { ryml::NodeRef n = tree["flow map, singleline"]; CHECK(tostr(n) == "flow map, singleline: {flow key: flow val}\n"); n.set_container_style(ryml::BLOCK); n["flow key"].set_val_style(ryml::VAL_LITERAL); CHECK(tostr(n) == "flow map, singleline:\n flow key: |-\n flow val\n"); } // next example { ryml::NodeRef n = tree["flow map, multiline"]; CHECK(tostr(n) == "flow map, multiline: {\n flow key: flow val\n }\n"); n.set_container_style(ryml::BLOCK); CHECK(tostr(n) == "flow map, multiline:\n flow key: flow val\n"); } // next example { ryml::NodeRef n = tree["flow seq, singleline"]; CHECK(tostr(n) == "flow seq, singleline: [flow val,flow val]\n"); n.set_key_style(ryml::KEY_FOLDED); n.set_container_style(ryml::BLOCK); n[0].set_val_style(ryml::VAL_SQUO); n[1].set_val_style(ryml::VAL_DQUO); CHECK(tostr(n) == "? >-\n flow seq, singleline\n:\n - 'flow val'\n - \"flow val\"\n"); } // next example { ryml::NodeRef n = tree["flow seq, multiline"]; CHECK(tostr(n) == "flow seq, multiline: [\n flow val,\n flow val\n ]\n"); n.set_container_style(ryml::FLOW_SL); CHECK(tostr(n) == "flow seq, multiline: [flow val,flow val]\n"); } // note the full tree now: CHECK(tostr(tree) != yaml); CHECK(tostr(tree) == R"('block map': {block key: block val} "block seq": [ block val 1, block val 2, quoted ] flow map, singleline: flow key: |- flow val ? >- flow seq, singleline : - 'flow val' - "flow val" flow map, multiline: flow key: flow val flow seq, multiline: [flow val,flow val] )"); // you can clear the style of single nodes: tree["block map"].clear_style(); tree["block seq"].clear_style(); CHECK(tostr(tree) == R"(block map: block key: block val block seq: - block val 1 - block val 2 - quoted flow map, singleline: flow key: |- flow val ? >- flow seq, singleline : - 'flow val' - "flow val" flow map, multiline: flow key: flow val flow seq, multiline: [flow val,flow val] )"); // you can clear the style recursively: tree.rootref().clear_style(/*recurse*/true); // when emitting nodes which have no style set, ryml will default // to block format for containers, and call // ryml::scalar_style_choose() to pick the style for each scalar // (at the cost of a scan over each scalar). Note that ryml picks // single-quoted for scalars containing commas: CHECK(tostr(tree) == R"(block map: block key: block val block seq: - block val 1 - block val 2 - quoted 'flow map, singleline': flow key: flow val 'flow seq, singleline': - flow val - flow val 'flow map, multiline': flow key: flow val 'flow seq, multiline': - flow val - flow val )"); // you can set the style based on type conditions: // // eg, set a single key to single-quoted tree["block map"].set_style_conditionally(/*type_mask*/ryml::KEY, /*remflags*/ryml::KEY_STYLE, /*addflags*/ryml::KEY_SQUO, /*recurse*/false); CHECK(tostr(tree) == R"('block map': block key: block val block seq: - block val 1 - block val 2 - quoted 'flow map, singleline': flow key: flow val 'flow seq, singleline': - flow val - flow val 'flow map, multiline': flow key: flow val 'flow seq, multiline': - flow val - flow val )"); // change all keys to single-quoted: tree.rootref().set_style_conditionally(/*type_mask*/ryml::KEY, /*remflags*/ryml::KEY_STYLE, /*addflags*/ryml::KEY_SQUO, /*recurse*/true); // change all vals to double-quoted tree.rootref().set_style_conditionally(/*type_mask*/ryml::VAL, /*remflags*/ryml::VAL_STYLE, /*addflags*/ryml::VAL_DQUO, /*recurse*/true); // change all seqs to flow tree.rootref().set_style_conditionally(/*type_mask*/ryml::SEQ, /*remflags*/ryml::CONTAINER_STYLE, /*addflags*/ryml::FLOW_SL, /*recurse*/true); // change all maps to flow tree.rootref().set_style_conditionally(/*type_mask*/ryml::MAP, /*remflags*/ryml::CONTAINER_STYLE, /*addflags*/ryml::BLOCK, /*recurse*/true); // done! CHECK(tostr(tree) == R"('block map': 'block key': "block val" 'block seq': ["block val 1","block val 2","quoted"] 'flow map, singleline': 'flow key': "flow val" 'flow seq, singleline': ["flow val","flow val"] 'flow map, multiline': 'flow key': "flow val" 'flow seq, multiline': ["flow val","flow val"] )"); // you can also set a conditional style in a single node (or its branch if recurse is true): tree["flow seq, singleline"].set_style_conditionally(/*type_mask*/ryml::SEQ, /*remflags*/ryml::CONTAINER_STYLE, /*addflags*/ryml::BLOCK, /*recurse*/false); CHECK(tostr(tree) == R"('block map': 'block key': "block val" 'block seq': ["block val 1","block val 2","quoted"] 'flow map, singleline': 'flow key': "flow val" 'flow seq, singleline': - "flow val" - "flow val" 'flow map, multiline': 'flow key': "flow val" 'flow seq, multiline': ["flow val","flow val"] )"); // see also: // - ryml::scalar_style_choose() // - ryml::scalar_style_json_choose() // - ryml::scalar_style_query_squo() // - ryml::scalar_style_query_plain() } //----------------------------------------------------------------------------- /** [experimental] control the indentation of emitted FLOW_ML containers */ void sample_style_flow_ml_indent() { // we will be using this helper throughout this function auto tostr = [](ryml::ConstNodeRef n, ryml::EmitOptions opts) { return ryml::emitrs_yaml(n, opts); }; ryml::csubstr yaml = "{map: {seq: [0, 1, 2, 3, [40, 41]]}}"; ryml::Tree tree = ryml::parse_in_arena(yaml); ryml::EmitOptions defaults = {}; ryml::EmitOptions noindent = ryml::EmitOptions{}.indent_flow_ml(false); CHECK(tostr(tree, defaults) == "{map: {seq: [0,1,2,3,[40,41]]}}"); // let's now set the style to FLOW_ML (it was FLOW_SL) tree.rootref().set_container_style(ryml::FLOW_ML); tree["map"].set_container_style(ryml::FLOW_ML); tree["map"]["seq"].set_container_style(ryml::FLOW_ML); tree["map"]["seq"][4].set_container_style(ryml::FLOW_ML); // by default FLOW_ML prints one value per line, indented: CHECK(tostr(tree, defaults) == R"({ map: { seq: [ 0, 1, 2, 3, [ 40, 41 ] ] } } )"); // if we use the noindent options, then each value is put at the // beginning of the line CHECK(tostr(tree, noindent) == R"({ map: { seq: [ 0, 1, 2, 3, [ 40, 41 ] ] } } )"); // Note that the noindent option will safely respect any prior // indent level from enclosing block containers! For example: tree.rootref().set_container_style(ryml::BLOCK); CHECK(tostr(tree, noindent) == // notice it is indented at the map level R"(map: { seq: [ 0, 1, 2, 3, [ 40, 41 ] ] } )"); // Let's set it one BLOCK level further: tree["map"].set_container_style(ryml::BLOCK); CHECK(tostr(tree, noindent) == // notice it is indented one more level R"(map: seq: [ 0, 1, 2, 3, [ 40, 41 ] ] )"); } //----------------------------------------------------------------------------- /** [experimental] set the parser to pick FLOW_SL even if the * container being parsed is FLOW_ML */ void sample_style_flow_ml_filter() { ryml::csubstr yaml = R"({ map: { seq: [ 0, 1, 2, 3, [ 40, 41 ] ] } } )"; ryml::csubstr yaml_not_indented = R"({ map: { seq: [ 0, 1, 2, 3, [ 40, 41 ] ] } } )"; // note that the parser defaults to detect multiline flow // (FLOW_ML) containers: { const ryml::Tree tree = ryml::parse_in_arena(yaml); CHECK(tree["map"].is_flow_ml()); // etc // emitted yaml is exactly equal to parsed yaml: CHECK(ryml::emitrs_yaml(tree) == yaml); } // if you prefer to shorten the emitted yaml, you can set the // parser to set singleline flow (FLOW_SL) on all flow containers: { const ryml::ParserOptions opts = ryml::ParserOptions{}.detect_flow_ml(false); const ryml::Tree tree = ryml::parse_in_arena(yaml, opts); CHECK(tree["map"].is_flow_sl()); // etc // notice how this is smaller now: CHECK(ryml::emitrs_yaml(tree) == R"({map: {seq: [0,1,2,3,[40,41]]}})"); } // you can also keep FLOW_ML, but control its indentation: // (see more details in @ref sample_style_flow_ml_indent()) { const ryml::EmitOptions noindent = ryml::EmitOptions{}.indent_flow_ml(false); const ryml::Tree tree = ryml::parse_in_arena(yaml); CHECK(tree["map"].is_flow_ml()); // etc CHECK(ryml::emitrs_yaml(tree, noindent) == yaml_not_indented); } } //----------------------------------------------------------------------------- /** shows how to parse and emit JSON. * * To emit YAML parsed from JSON, see also @ref sample_style() for * info on clearing the style flags (example below). */ void sample_json() { ryml::csubstr json = R"({ "doe": "a deer, a female deer", "ray": "a drop of golden sun", "me": "a name, I call myself", "far": "a long long way to go" } )"; // Since JSON is a subset of YAML, parsing JSON is just the // same as YAML: ryml::Tree tree = ryml::parse_in_arena(json); // If you are sure the source is valid json, you can use the // appropriate parse_json overload, which is faster because json // has a smaller grammar: ryml::Tree json_tree = ryml::parse_json_in_arena(json); // to emit JSON: CHECK(ryml::emitrs_json(tree) == json); CHECK(ryml::emitrs_json(json_tree) == json); // to emit JSON to a stream: std::stringstream ss; ss << ryml::as_json(tree); // <- mark it like this CHECK(ss.str() == json); // Note the following limitations: // // - YAML streams cannot be emitted as json, and are not // allowed. But you can work around this by emitting the // individual documents separately; see the sample_docs() // below for such an example. // // - tags cannot be emitted as json, and are not allowed. // // - anchors and references cannot be emitted as json and // are not allowed. // // Note that when parsing JSON, ryml will the style of each node // in the JSON. This means that if you emit as YAML it will look // mostly the same as the JSON: std::cout << ryml::emitrs_yaml(json_tree); CHECK(ryml::emitrs_yaml(json_tree) == json); // If you want to avoid this, you will need to clear the style. json_tree.rootref().clear_style(); // clear the style of the map, but do not recurse // note that this is now block mode. That is because when no // style is set, the YAML emit function will default to block mode. CHECK(ryml::emitrs_yaml(json_tree) == R"("doe": "a deer, a female deer" "ray": "a drop of golden sun" "me": "a name, I call myself" "far": "a long long way to go" )"); // if you don't want the double quotes in the scalar, you can // recurse: json_tree.rootref().clear_style(/*recurse*/true); // so now when emitting you will get this: // (the scalars with a comma are single-quote) CHECK(ryml::emitrs_yaml(json_tree) == R"(doe: 'a deer, a female deer' ray: a drop of golden sun me: 'a name, I call myself' far: a long long way to go )"); // you can do custom style changes based on a type mask. this // will change set the style of all scalar values to single-quoted json_tree.rootref().set_style_conditionally(ryml::VAL, /*remflags*/ryml::VAL_STYLE, /*addflags*/ryml::VAL_SQUO, /*recurse*/true); CHECK(ryml::emitrs_yaml(json_tree) == R"(doe: 'a deer, a female deer' ray: 'a drop of golden sun' me: 'a name, I call myself' far: 'a long long way to go' )"); // see in particular sample_style() for more examples } //----------------------------------------------------------------------------- /** demonstrates usage with anchors and alias references. Note that dereferencing is opt-in; after parsing, you have to call @ref c4::yml::Tree::resolve() explicitly if you want resolved references in the tree. This method will resolve all references and substitute the anchored values in place of the reference. The @ref c4::yml::Tree::resolve() method first does a full traversal of the tree to gather all anchors and references in a separate collection, then it goes through that collection to locate the names, which it does by obeying the YAML standard diktat that > an alias node refers to the most recent node in > the serialization having the specified anchor So, depending on the number of anchor/alias nodes, this is a potentially expensive operation, with a best-case linear complexity (from the initial traversal) and a worst-case quadratic complexity (if every node has an alias/anchor). This potential cost is the reason for requiring an explicit call to @ref c4::yml::Tree::resolve() */ void sample_anchors_and_aliases() { std::string unresolved = R"(base: &base name: Everyone has same name foo: &foo <<: *base age: 10 bar: &bar <<: *base age: 20 bill_to: &id001 street: |- 123 Tornado Alley Suite 16 city: East Centerville state: KS ship_to: *id001 &keyref key: &valref val *valref : *keyref )"; std::string resolved = R"(base: name: Everyone has same name foo: name: Everyone has same name age: 10 bar: name: Everyone has same name age: 20 bill_to: street: |- 123 Tornado Alley Suite 16 city: East Centerville state: KS ship_to: street: |- 123 Tornado Alley Suite 16 city: East Centerville state: KS key: val val: key )"; ryml::Tree tree = ryml::parse_in_arena(ryml::to_csubstr(unresolved)); // by default, references are not resolved when parsing: CHECK( ! tree["base"].has_key_anchor()); CHECK( tree["base"].has_val_anchor()); CHECK( tree["base"].val_anchor() == "base"); CHECK( tree["key"].key_anchor() == "keyref"); CHECK( tree["key"].val_anchor() == "valref"); CHECK( tree["*valref"].is_key_ref()); CHECK( tree["*valref"].is_val_ref()); CHECK( tree["*valref"].key_ref() == "valref"); CHECK( tree["*valref"].val_ref() == "keyref"); // to resolve references, simply call tree.resolve(), // which will perform the reference instantiations: tree.resolve(); // all the anchors and references are substistuted and then removed: CHECK( ! tree["base"].has_key_anchor()); CHECK( ! tree["base"].has_val_anchor()); CHECK( ! tree["base"].has_val_anchor()); CHECK( ! tree["key"].has_key_anchor()); CHECK( ! tree["key"].has_val_anchor()); CHECK( ! tree["val"].is_key_ref()); // notice *valref is now turned to val CHECK( ! tree["val"].is_val_ref()); // notice *valref is now turned to val CHECK(tree["ship_to"]["city"].val() == "East Centerville"); CHECK(tree["ship_to"]["state"].val() == "KS"); } /** demonstrates how to use the API to programatically create anchors * and aliases */ void sample_anchors_and_aliases_create() { // part 1: anchor/ref { ryml::Tree t; t.rootref() |= ryml::MAP|ryml::BLOCK; t["kanchor"] = "2"; t["kanchor"].set_key_anchor("kanchor"); t["vanchor"] = "3"; t["vanchor"].set_val_anchor("vanchor"); // to set a reference, need to call .set_val_ref()/.set_key_ref() t["kref"].set_val_ref("kanchor"); t["vref"].set_val_ref("vanchor"); t["nref"] = "*vanchor"; // NOTE: this is not set as a reference in the tree! CHECK(ryml::emitrs_yaml(t) == R"(&kanchor kanchor: 2 vanchor: &vanchor 3 kref: *kanchor vref: *vanchor nref: '*vanchor' )"); // note that ryml emits nref with quotes to disambiguate (because no style was set) t.resolve(); CHECK(ryml::emitrs_yaml(t) == R"(kanchor: 2 vanchor: 3 kref: kanchor vref: 3 nref: '*vanchor' )"); // note that nref was not resolved } // part 2: simple inheritance (ie, adding `<<: *anchor` nodes) { ryml::Tree t = ryml::parse_in_arena(R"( orig: &orig {foo: bar, baz: bat} copy: {} notcopy: {} notref: {} )"); t["copy"]["<<"].set_val_ref("orig"); t["notcopy"]["test"].set_val_ref("orig"); t["notcopy"]["<<"].set_val_ref("orig"); t["notref"]["<<"] = "*orig"; // not a reference! .set_val_ref() was not called CHECK(ryml::emitrs_yaml(t) == R"(orig: &orig {foo: bar,baz: bat} copy: {<<: *orig} notcopy: {test: *orig,<<: *orig} notref: {<<: '*orig'} )"); t.resolve(); CHECK(ryml::emitrs_yaml(t) == R"(orig: {foo: bar,baz: bat} copy: {foo: bar,baz: bat} notcopy: {test: {foo: bar,baz: bat},foo: bar,baz: bat} notref: {<<: '*orig'} )"); } // part 3: multiple inheritance (ie, `<<: [*ref1,*ref2,*etc]`) { ryml::Tree t = ryml::parse_in_arena(R"(orig1: &orig1 {foo: bar} orig2: &orig2 {baz: bat} orig3: &orig3 {and: more} copy: {} )"); ryml::NodeRef seq = t["copy"]["<<"]; seq |= ryml::SEQ; seq.append_child().set_val_ref("orig1"); seq.append_child().set_val_ref("orig2"); seq.append_child().set_val_ref("orig3"); CHECK(ryml::emitrs_yaml(t) == R"(orig1: &orig1 {foo: bar} orig2: &orig2 {baz: bat} orig3: &orig3 {and: more} copy: {<<: [*orig1,*orig2,*orig3]} )"); t.resolve(); CHECK(ryml::emitrs_yaml(t) == R"(orig1: {foo: bar} orig2: {baz: bat} orig3: {and: more} copy: {foo: bar,baz: bat,and: more} )"); } } //----------------------------------------------------------------------------- void sample_tags() { const std::string yaml = R"(--- !!map a: 0 b: 1 --- !map a: b --- !!seq - a - b --- !!str a b --- !!str 'a: b' --- !!str a: b --- !!set ? a ? b --- !!set a: --- !!seq - !!int 0 - !!str 1 )"; const ryml::Tree tree = ryml::parse_in_arena(ryml::to_csubstr(yaml)); const ryml::ConstNodeRef root = tree.rootref(); CHECK(root.is_stream()); CHECK(root.num_children() == 9); for(ryml::ConstNodeRef doc : root.children()) CHECK(doc.is_doc()); // tags are kept verbatim from the source: CHECK(root[0].has_val_tag()); CHECK(root[0].val_tag() == "!!map"); // valid only if the node has a val tag CHECK(root[1].val_tag() == "!map"); CHECK(root[2].val_tag() == "!!seq"); CHECK(root[3].val_tag() == "!!str"); CHECK(root[4].val_tag() == "!!str"); CHECK(root[5]["a"].has_key_tag()); CHECK(root[5]["a"].key_tag() == "!!str"); // valid only if the node has a key tag CHECK(root[6].val_tag() == "!!set"); CHECK(root[7].val_tag() == "!!set"); CHECK(root[8].val_tag() == "!!seq"); CHECK(root[8][0].val_tag() == "!!int"); CHECK(root[8][1].val_tag() == "!!str"); // ryml also provides a complete toolbox to deal with tags. // there is an enumeration for the standard YAML tags: CHECK(ryml::to_tag("!map") == ryml::TAG_NONE); CHECK(ryml::to_tag("!!map") == ryml::TAG_MAP); CHECK(ryml::to_tag("!!seq") == ryml::TAG_SEQ); CHECK(ryml::to_tag("!!str") == ryml::TAG_STR); CHECK(ryml::to_tag("!!int") == ryml::TAG_INT); CHECK(ryml::to_tag("!!set") == ryml::TAG_SET); // given a tag enum, you can fetch the short tag string: CHECK(ryml::from_tag(ryml::TAG_NONE) == ""); CHECK(ryml::from_tag(ryml::TAG_MAP) == "!!map"); CHECK(ryml::from_tag(ryml::TAG_SEQ) == "!!seq"); CHECK(ryml::from_tag(ryml::TAG_STR) == "!!str"); CHECK(ryml::from_tag(ryml::TAG_INT) == "!!int"); CHECK(ryml::from_tag(ryml::TAG_SET) == "!!set"); // you can also fetch the long tag string: CHECK(ryml::from_tag_long(ryml::TAG_NONE) == ""); CHECK(ryml::from_tag_long(ryml::TAG_MAP) == ""); CHECK(ryml::from_tag_long(ryml::TAG_SEQ) == ""); CHECK(ryml::from_tag_long(ryml::TAG_STR) == ""); CHECK(ryml::from_tag_long(ryml::TAG_INT) == ""); CHECK(ryml::from_tag_long(ryml::TAG_SET) == ""); // and likewise: CHECK(ryml::to_tag("!map") == ryml::TAG_NONE); CHECK(ryml::to_tag("") == ryml::TAG_MAP); CHECK(ryml::to_tag("") == ryml::TAG_SEQ); CHECK(ryml::to_tag("") == ryml::TAG_STR); CHECK(ryml::to_tag("") == ryml::TAG_INT); CHECK(ryml::to_tag("") == ryml::TAG_SET); // to normalize a tag as much as possible, use normalize_tag(): CHECK(ryml::normalize_tag("!!map") == "!!map"); CHECK(ryml::normalize_tag("!") == "!!map"); CHECK(ryml::normalize_tag("") == "!!map"); CHECK(ryml::normalize_tag("tag:yaml.org,2002:map") == "!!map"); CHECK(ryml::normalize_tag("!") == ""); CHECK(ryml::normalize_tag("!map") == "!map"); CHECK(ryml::normalize_tag("!my!foo") == "!my!foo"); // and also for the long form: CHECK(ryml::normalize_tag_long("!!map") == ""); CHECK(ryml::normalize_tag_long("!") == ""); CHECK(ryml::normalize_tag_long("") == ""); CHECK(ryml::normalize_tag_long("tag:yaml.org,2002:map") == ""); CHECK(ryml::normalize_tag_long("!") == ""); CHECK(ryml::normalize_tag_long("!map") == "!map"); // The tree provides the following methods applying to every node // with a key and/or val tag: ryml::Tree normalized_tree = tree; normalized_tree.normalize_tags(); // normalize all tags in short form CHECK(ryml::emitrs_yaml(normalized_tree) == R"(--- !!map a: 0 b: 1 --- !map a: b --- !!seq - a - b --- !!str a b --- !!str 'a: b' --- !!str a: b --- !!set a: b: --- !!set a: --- !!seq - !!int 0 - !!str 1 )"); ryml::Tree normalized_tree_long = tree; normalized_tree_long.normalize_tags_long(); // normalize all tags in short form CHECK(ryml::emitrs_yaml(normalized_tree_long) == R"(--- ! a: 0 b: 1 --- !map a: b --- ! - a - b --- ! a b --- ! 'a: b' --- ! a: b --- ! a: b: --- ! a: --- ! - ! 0 - ! 1 )"); } //----------------------------------------------------------------------------- void sample_tag_directives() { const std::string yaml = R"( %TAG !m! !my- --- # Bulb here !m!light fluorescent ... %TAG !m! !meta- --- # Color here !m!light green )"; ryml::Tree tree = ryml::parse_in_arena(ryml::to_csubstr(yaml)); CHECK(ryml::emitrs_yaml(tree) == R"(%TAG !m! !my- --- !m!light fluorescent ... %TAG !m! !meta- --- !m!light green )"); // tags are not resolved by default. Use .resolve_tags() to // accomplish this: tree.resolve_tags(); CHECK(ryml::emitrs_yaml(tree) == R"(%TAG !m! !my- --- ! fluorescent ... %TAG !m! !meta- --- ! green )"); // see also tree.normalize_tags() // see also tree.normalize_tags_long() } //----------------------------------------------------------------------------- void sample_docs() { std::string yml = R"(--- a: 0 b: 1 --- c: 2 d: 3 --- - 4 - 5 - 6 - 7 )"; ryml::Tree tree = ryml::parse_in_place(ryml::to_substr(yml)); CHECK(ryml::emitrs_yaml(tree) == yml); // iteration through docs { // using the node API const ryml::ConstNodeRef stream = tree.rootref(); CHECK(stream.is_root()); CHECK(stream.is_stream()); CHECK(!stream.is_doc()); CHECK(stream.num_children() == 3); for(const ryml::ConstNodeRef doc : stream.children()) CHECK(doc.is_doc()); CHECK(tree.docref(0).id() == stream.child(0).id()); CHECK(tree.docref(1).id() == stream.child(1).id()); CHECK(tree.docref(2).id() == stream.child(2).id()); // equivalent: using the lower level index API const ryml::id_type stream_id = tree.root_id(); CHECK(tree.is_root(stream_id)); CHECK(tree.is_stream(stream_id)); CHECK(!tree.is_doc(stream_id)); CHECK(tree.num_children(stream_id) == 3); for(ryml::id_type doc_id = tree.first_child(stream_id); doc_id != ryml::NONE; doc_id = tree.next_sibling(stream_id)) CHECK(tree.is_doc(doc_id)); CHECK(tree.doc(0) == tree.child(stream_id, 0)); CHECK(tree.doc(1) == tree.child(stream_id, 1)); CHECK(tree.doc(2) == tree.child(stream_id, 2)); // using the node API CHECK(stream[0].is_doc()); CHECK(stream[0].is_map()); CHECK(stream[0]["a"].val() == "0"); CHECK(stream[0]["b"].val() == "1"); // equivalent: using the index API const ryml::id_type doc0_id = tree.first_child(stream_id); CHECK(tree.is_doc(doc0_id)); CHECK(tree.is_map(doc0_id)); CHECK(tree.val(tree.find_child(doc0_id, "a")) == "0"); CHECK(tree.val(tree.find_child(doc0_id, "b")) == "1"); // using the node API CHECK(stream[1].is_doc()); CHECK(stream[1].is_map()); CHECK(stream[1]["c"].val() == "2"); CHECK(stream[1]["d"].val() == "3"); // equivalent: using the index API const ryml::id_type doc1_id = tree.next_sibling(doc0_id); CHECK(tree.is_doc(doc1_id)); CHECK(tree.is_map(doc1_id)); CHECK(tree.val(tree.find_child(doc1_id, "c")) == "2"); CHECK(tree.val(tree.find_child(doc1_id, "d")) == "3"); // using the node API CHECK(stream[2].is_doc()); CHECK(stream[2].is_seq()); CHECK(stream[2][0].val() == "4"); CHECK(stream[2][1].val() == "5"); CHECK(stream[2][2].val() == "6"); CHECK(stream[2][3].val() == "7"); // equivalent: using the index API const ryml::id_type doc2_id = tree.next_sibling(doc1_id); CHECK(tree.is_doc(doc2_id)); CHECK(tree.is_seq(doc2_id)); CHECK(tree.val(tree.child(doc2_id, 0)) == "4"); CHECK(tree.val(tree.child(doc2_id, 1)) == "5"); CHECK(tree.val(tree.child(doc2_id, 2)) == "6"); CHECK(tree.val(tree.child(doc2_id, 3)) == "7"); } // Note: since json does not have streams, you cannot emit the above // tree as json when you start from the root: //CHECK(ryml::emitrs_json(tree) == yml); // RUNTIME ERROR! // but, althouth emitting streams as json is not possible, // you can iterate through individual documents and emit // them separately: { const std::string expected_json[] = { "{\n \"a\": 0,\n \"b\": 1\n}\n", "{\n \"c\": 2,\n \"d\": 3\n}\n", "[\n 4,\n 5,\n 6,\n 7\n]\n", }; // using the node API { ryml::id_type count = 0; const ryml::ConstNodeRef stream = tree.rootref(); CHECK(stream.num_children() == (ryml::id_type)C4_COUNTOF(expected_json)); for(ryml::ConstNodeRef doc : stream.children()) { CHECK(ryml::emitrs_json(doc) == expected_json[count++]); } } // equivalent: using the index API { ryml::id_type count = 0; const ryml::id_type stream_id = tree.root_id(); CHECK(tree.num_children(stream_id) == (ryml::id_type)C4_COUNTOF(expected_json)); for(ryml::id_type doc_id = tree.first_child(stream_id); doc_id != ryml::NONE; doc_id = tree.next_sibling(doc_id)) { CHECK(ryml::emitrs_json(tree, doc_id) == expected_json[count++]); } } } } //----------------------------------------------------------------------------- // To avoid imposing a particular type of error handling, ryml uses // error handler callbacks. This enables users to use exceptions, or // setjmp()/longjmp(), or plain calls to abort(), as they see fit. // // However, it is important to note that the error callbacks must never // return to the caller! Otherwise, an infinite loop or program crash // will likely occur. // // For this reason, to recover from an error when exceptions are disabled, // then a non-local jump must be performed using setjmp()/longjmp(). // The code below demonstrates both flows. // // ryml provides default error handlers, which call // std::abort(). You can use the cmake option and the macro // RYML_DEFAULT_CALLBACK_USES_EXCEPTIONS to have the default error // handler throw an exception instead. /** demonstrates how to set a custom error handler for ryml */ void sample_error_handler() { ErrorHandlerExample errh; // browse this class to understand more details errh.check_disabled(); // set the global error handlers. Note the error callbacks must // never return: they must either throw an exception, use setjmp() // and longjmp(), or abort. Otherwise, the parser will enter into // an infinite loop, or the program may crash. ryml::set_callbacks(errh.callbacks()); errh.check_enabled(); CHECK(errh.check_error_occurs([&]{ ryml::Tree tree = ryml::parse_in_arena("errorhandler.yml", "[a: b\n}"); })); ryml::set_callbacks(errh.defaults); // restore defaults. errh.check_disabled(); } //----------------------------------------------------------------------------- void sample_error_basic() { auto cause_basic_error = []{ ryml::TagDirective tag = {}; return tag.create_from_str("%%%TAG abc"); }; { ScopedErrorHandlerExample errh; // set the example callbacks (scoped) CHECK(errh.check_error_occurs(cause_basic_error)); } #ifdef _RYML_WITH_EXCEPTIONS bool gotit = false; try { cause_basic_error(); } catch(ryml::ExceptionBasic const& exc) { gotit = true; ryml::csubstr msg = ryml::to_csubstr(exc.what()); CHECK(!exc.errdata_basic.location.name.empty()); CHECK(!msg.empty()); } CHECK(gotit); #endif } void sample_error_parse() { ryml::csubstr ymlsrc = R"({ a: b [ )"; ryml::csubstr ymlfile = "file.yml"; auto cause_parse_error = [&]{ return ryml::parse_in_arena(ymlfile, ymlsrc); }; // the YAML in ymlsrc must cause a parse error while it is being // parsed. We use our error handler to catch that error, and save // the error info: ErrorHandlerExample errh; { ryml::set_callbacks(errh.callbacks()); CHECK(errh.check_error_occurs(cause_parse_error)); // the handler in errh saves the error info in itself. Let's // use that to see the messages we get. // // this message is the short message passed into the parse // error handler: CHECK(errh.saved_msg_short == "invalid character: '['"); // this message was created inside the handler, by calling // ryml::err_parse_format(): CHECK(ryml::to_csubstr(errh.saved_msg_full).begins_with("file.yml:3: col=4 (12B): ERROR: [parse] invalid character: '['")); // If you keep the YAML source buffer around, you can also use // it to create/print a larger error message showing the // YAML source code context which causes the error: std::string msg_ctx = errh.saved_msg_full + "\n"; ryml::location_format_with_context([&msg_ctx](ryml::csubstr s){ msg_ctx.append(s.str, s.len); }, errh.saved_parse_loc, ymlsrc, "err"); CHECK(ryml::to_csubstr(msg_ctx).begins_with("file.yml:3: col=4 (12B): ERROR: [parse] invalid character: '['")); CHECK(ryml::to_csubstr(msg_ctx).ends_with(R"(file.yml:3: col=4 (12B): err: err: err: [ err: | err: (here) err: err: see region: err: err: { err: a: b err: [ err: | err: (here) )")); // // Let's now check the location (see the message above): CHECK(errh.saved_parse_loc.name == ymlfile); CHECK(errh.saved_parse_loc.line == 3); CHECK(errh.saved_parse_loc.col == 4); CHECK(errh.saved_parse_loc.offset == 12); CHECK(errh.saved_parse_loc.offset <= ymlsrc.len); // ... and this is the location in the ryml source code file where // this error was found: CHECK(!errh.saved_basic_loc.name.empty()); CHECK(errh.saved_basic_loc.line > 0); CHECK(errh.saved_basic_loc.col > 0); CHECK(errh.saved_basic_loc.offset > 0); ryml::set_callbacks(errh.defaults); } // A parse error is also a basic error. If no parse error handler // is set, then ryml falls back to a basic error: { ryml::Callbacks cb = errh.callbacks(); cb.m_error_parse = nullptr; ryml::set_callbacks(cb); CHECK(ryml::get_callbacks().m_error_parse == nullptr); CHECK(errh.check_error_occurs(cause_parse_error)); // we got a basic error instead of a parse error: CHECK(errh.saved_msg_short == "invalid character: '['"); // notice that the full message now displays this as a basic // error: CHECK(errh.saved_msg_full == "file.yml:3: col=4 (12B): ERROR: [basic] invalid character: '['"); // the yml location is now in the location saved from the basic error CHECK(errh.saved_basic_loc.name == ymlfile); CHECK(errh.saved_basic_loc.line == 3); CHECK(errh.saved_basic_loc.col == 4); CHECK(errh.saved_basic_loc.offset == 12); CHECK(errh.saved_basic_loc.offset <= ymlsrc.len); CHECK(errh.saved_parse_loc.name == ryml::csubstr{}); CHECK(errh.saved_parse_loc.line == ryml::csubstr::npos); CHECK(errh.saved_parse_loc.col == ryml::csubstr::npos); CHECK(errh.saved_parse_loc.offset == ryml::csubstr::npos); ryml::set_callbacks(errh.defaults); } #ifdef _RYML_WITH_EXCEPTIONS bool gotit = false; try { cause_parse_error(); } catch(ryml::ExceptionParse const& exc) { gotit = true; ryml::csubstr msg = ryml::to_csubstr(exc.what()); CHECK(exc.errdata_parse.ymlloc.name == ymlfile); CHECK(exc.errdata_parse.ymlloc.line == 3); CHECK(exc.errdata_parse.ymlloc.col == 4); CHECK(exc.errdata_parse.ymlloc.offset == 12); CHECK(exc.errdata_parse.ymlloc.offset <= ymlsrc.len); // the message saved in the exception is just the concrete error description: CHECK(msg == "invalid character: '['"); // to print richer error messages, ryml provides helpers to // format that description into a complete error message, // containing location and source context indication: std::string full; auto dumpfn = [&full](ryml::csubstr s) { full.append(s.str, s.len); }; ryml::err_parse_format(dumpfn, msg, exc.errdata_parse); full += '\n'; ryml::location_format_with_context(dumpfn, exc.errdata_parse.ymlloc, ymlsrc, "err", 3); CHECK(ryml::to_csubstr(full).begins_with("file.yml:3: col=4 (12B): ERROR: [parse] invalid character: '['")); CHECK(ryml::to_csubstr(full).ends_with(R"(file.yml:3: col=4 (12B): err: err: err: [ err: | err: (here) err: err: see region: err: err: { err: a: b err: [ err: | err: (here) )")); } CHECK(gotit); gotit = false; try { cause_parse_error(); } catch(ryml::ExceptionBasic const& exc) // use references! don't slice the exception { gotit = true; ryml::csubstr msg = ryml::to_csubstr(exc.what()); CHECK(!exc.errdata_basic.location.name.empty()); CHECK(!msg.empty()); } CHECK(gotit); #endif } /** Visit errors happen when an error is triggered while reading from * a node. */ void sample_error_visit() { ryml::csubstr ymlfile = "file.yml"; ryml::csubstr ymlsrc = "float: 123.456"; ErrorHandlerExample errh; { ryml::set_callbacks(errh.callbacks()); ryml::Tree tree = ryml::parse_in_arena(ymlfile, ymlsrc); CHECK(errh.check_error_occurs([&]{ int intval = 0; tree["float"] >> intval; // cannot deserialize 123.456 to int })); // the handler in errh saves the error info in itself. Let's // use that to see the messages we get. // // this message is the short message passed into the visit error CHECK(errh.saved_msg_short == "could not deserialize value"); // this message was created inside the handler, by calling // ryml::err_visit_format(): CHECK(ryml::csubstr::npos != ryml::to_csubstr(errh.saved_msg_full).find("ERROR: [visit] could not deserialize value")); // The location of the visit error is of the C++ source file where // the error was detected -- NOT of the YAML source file: CHECK(errh.saved_basic_loc.name != ymlfile); // However, note that the tree and node id are available: CHECK(errh.saved_visit_tree == &tree); CHECK(errh.saved_visit_id == tree["float"].id()); // see sample_error_visit_location() for an example on how // to extract the location. } // visit errors also fall back to basic errors when the visit // handler is not set (similar to the behavior of ExceptionVisit): { ryml::Callbacks cb = errh.callbacks(); cb.m_error_visit = nullptr; ryml::set_callbacks(cb); CHECK(ryml::get_callbacks().m_error_visit == nullptr); ryml::Tree tree = ryml::parse_in_arena(ymlfile, ymlsrc); CHECK(errh.check_error_occurs([&]{ int intval = 0; tree["float"] >> intval; // cannot deserialize 123.456 to int })); // we got a basic error instead of a visit error: CHECK(errh.saved_msg_short == "could not deserialize value"); // notice that the full message now displays this as a basic // error: CHECK(ryml::csubstr::npos != ryml::to_csubstr(errh.saved_msg_full).find("ERROR: [basic] could not deserialize value")); // the tree and id are not set, because this was called as a basic error CHECK(errh.saved_visit_tree == nullptr); CHECK(errh.saved_visit_id == ryml::NONE); ryml::set_callbacks(errh.defaults); } #ifdef _RYML_WITH_EXCEPTIONS // when using the default ryml callbacks (see // RYML_NO_DEFAULT_CALLBACKS), and // RYML_DEFAULT_CALLBACK_USES_EXCEPTIONS is defined, the ryml // parse handler throws an exception of type ryml::ExceptionVisit, // which is derived from ryml::ExceptionBasic. { const ryml::Tree tree = ryml::parse_in_arena(ymlfile, ymlsrc); bool gotit = false; try { int intval = 0; tree["float"] >> intval; // cannot deserialize 123.456 to int } catch(ryml::ExceptionVisit const& exc) { gotit = true; ryml::csubstr msg = ryml::to_csubstr(exc.what()); CHECK(!exc.errdata_visit.cpploc.name.empty()); CHECK(exc.errdata_visit.tree == &tree); CHECK(exc.errdata_visit.node == tree["float"].id()); CHECK(!msg.empty()); } CHECK(gotit); } // you can also catch the exception as its base, // ryml::ExceptionBasic: { const ryml::Tree tree = ryml::parse_in_arena(ymlfile, ymlsrc); bool gotit = false; try { int intval = 0; tree["float"] >> intval; // cannot deserialize 123.456 to int } catch(ryml::ExceptionBasic const& exc) // use references! don't slice the exception { gotit = true; ryml::csubstr msg = ryml::to_csubstr(exc.what()); CHECK(!exc.errdata_basic.location.name.empty()); CHECK(!msg.empty()); } CHECK(gotit); } #endif } /** It is possible to obtain the YAML location from a visit * error: when the tree is obtained from parsing YAML, the messages * may be enriched by using a parser set to track the locations. See * @ref sample_location_tracking() for more details on how to use * locations. */ void sample_error_visit_location() { ScopedErrorHandlerExample errh; // we will use locations to show the YAML source context of the // node where the visit error was triggered. This is a very // convenient feature to show detailed messages when deserializing // data read from a file (but do note this is opt-in, and it is // not mandatory). See sample_location_tracking() for more details // on location tracking. ryml::ParserOptions opts = ryml::ParserOptions{}.locations(true); ryml::EventHandlerTree evt_handler{}; ryml::Parser parser(&evt_handler, opts); ryml::csubstr ymlfile = "file.yml"; ryml::csubstr ymlsrc = R"(foo: bar char: a int: a float: 123.456 )"; const ryml::Tree tree = ryml::parse_in_arena(&parser, ymlfile, ymlsrc); // This function will cause a visit error when being called: auto cause_visit_error = [&]{ int intval = 0; tree["float"] >> intval; // cannot deserialize 123.456 to int }; // Like with the parse error, we will use our error handler to // catch that visit error, and save the error info: CHECK(evt_handler.callbacks() == errh.callbacks()); CHECK(parser.callbacks() == errh.callbacks()); CHECK(tree.callbacks() == errh.callbacks()); { CHECK(errh.check_error_occurs(cause_visit_error)); // the handler in errh saves the error info in itself. Let's // use that to see the messages we get. // // this message is the short message passed into the visit error CHECK(errh.saved_msg_short == "could not deserialize value"); // this message was created inside the handler, by calling // ryml::err_visit_format(): CHECK(ryml::csubstr::npos != ryml::to_csubstr(errh.saved_msg_full).find("ERROR: [visit] could not deserialize value")); // The location of the visit error is of the C++ source file where // the error was detected -- NOT of the YAML source file: CHECK(errh.saved_basic_loc.name != ymlfile); // However, note that the tree and node id are available: CHECK(errh.saved_visit_tree == &tree); CHECK(errh.saved_visit_id == tree["float"].id()); // ... which we can use to get the location in the YAML source // from the parser (but see @ref sample_location_tracking()): ryml::Location ymlloc = errh.saved_visit_tree->location(parser, errh.saved_visit_id); CHECK(ymlloc.name == ymlfile); // In turn, we can use format_location_context() to // print/create an error message pointing at the YAML source // code: std::string msg = errh.saved_msg_full; ryml::location_format_with_context([&msg](ryml::csubstr s){ msg.append(s.str, s.len); }, ymlloc, ymlsrc, "err", /*number of lines to show before the error*/3); CHECK(ryml::to_csubstr(msg).ends_with(R"(file.yml:3: col=0 (24B): err: err: err: float: 123.456 err: | err: (here) err: err: see region: err: err: foo: bar err: char: a err: int: a err: float: 123.456 err: | err: (here) )")); } } //----------------------------------------------------------------------------- // Please note the following about the use of custom allocators with // ryml. Due to [the static initialization order // fiasco](https://en.cppreference.com/w/cpp/language/siof), if you // use static ryml trees or parsers, you need to make sure that their // callbacks have the same lifetime. So you can't use ryml's default // callbacks structure, as it is declared in a ryml file, and the standard // provides no guarantee on the relative initialization order, such // that it is constructed before and destroyed after your // variables (in fact you are pretty much guaranteed to see this // fail). So please carefully consider your choices, and ponder // whether you really need to use ryml static trees and parsers. If // you do need this, then you will need to declare and use a ryml // callbacks structure that outlives the tree and/or parser. // // See also sample_static_trees() for an example on how to use // trees with static lifetime. /** @addtogroup doc_sample_helpers * @{ */ struct GlobalAllocatorExample { std::vector memory_pool = std::vector(10u * 1024u); // 10KB size_t num_allocs = 0, alloc_size = 0, corr_size = 0; size_t num_deallocs = 0, dealloc_size = 0; void *allocate(size_t len) { void *ptr = &memory_pool[alloc_size]; alloc_size += len; ++num_allocs; // ensure the ptr is aligned uintptr_t uptr = (uintptr_t)ptr; const uintptr_t align = alignof(max_align_t); if (uptr % align) { uintptr_t prev = uptr - (uptr % align); uintptr_t next = prev + align; uintptr_t corr = next - uptr; ptr = (void*)(((char*)ptr) + corr); corr_size += corr; } C4_CHECK_MSG(alloc_size + corr_size <= memory_pool.size(), "out of memory! requested=%zu+%zu available=%zu\n", alloc_size, corr_size, memory_pool.size()); return ptr; } void free(void *mem, size_t len) { CHECK((char*)mem >= &memory_pool.front() && (char*)mem < &memory_pool.back()); CHECK((char*)mem+len >= &memory_pool.front() && (char*)mem+len <= &memory_pool.back()); dealloc_size += len; ++num_deallocs; // no need to free here } // bridge ryml::Callbacks callbacks() { ryml::Callbacks cb = {}; return cb.set_user_data(this) .set_allocate(&GlobalAllocatorExample::s_allocate) .set_free(&GlobalAllocatorExample::s_free); } static void* s_allocate(size_t len, void* /*hint*/, void *this_) { return ((GlobalAllocatorExample*)this_)->allocate(len); } static void s_free(void *mem, size_t len, void *this_) { ((GlobalAllocatorExample*)this_)->free(mem, len); } // checking ~GlobalAllocatorExample() { check_and_reset(); } void check_and_reset() { std::cout << "size: alloc=" << alloc_size << " dealloc=" << dealloc_size << std::endl; std::cout << "count: #allocs=" << num_allocs << " #deallocs=" << num_deallocs << std::endl; CHECK(num_allocs == num_deallocs); CHECK(alloc_size >= dealloc_size); // failure here means a double free CHECK(alloc_size == dealloc_size); // failure here means a leak num_allocs = 0; num_deallocs = 0; alloc_size = 0; dealloc_size = 0; } }; /** @} */ /** demonstrates how to set the global allocator for ryml */ void sample_global_allocator() { GlobalAllocatorExample mem; // save the existing callbacks for restoring ryml::Callbacks defaults = ryml::get_callbacks(); // set to our callbacks ryml::set_callbacks(mem.callbacks()); // verify that the allocator is in effect ryml::Callbacks const& current = ryml::get_callbacks(); CHECK(current.m_allocate == &mem.s_allocate); CHECK(current.m_free == &mem.s_free); // so far nothing was allocated CHECK(mem.alloc_size == 0); // parse one tree and check (void)ryml::parse_in_arena(R"({foo: bar})"); mem.check_and_reset(); // parse another tree and check (void)ryml::parse_in_arena(R"([a, b, c, d, {foo: bar, money: pennys}])"); mem.check_and_reset(); // verify that by reserving we save allocations { ryml::EventHandlerTree evt_handler; ryml::Parser parser(&evt_handler); // reuse a parser ryml::Tree tree; // reuse a tree tree.reserve(10); // reserve the number of nodes tree.reserve_arena(100); // reserve the arena size parser.reserve_stack(10); // reserve the parser depth. // since the parser stack uses Small Storage Optimization, // allocations will only happen with capacities higher than 16. CHECK(mem.num_allocs == 2); // tree, tree_arena and NOT the parser parser.reserve_stack(20); // reserve the parser depth. CHECK(mem.num_allocs == 3); // tree, tree_arena and now the parser as well // verify that no other allocations occur when parsing size_t size_before = mem.alloc_size; parse_in_arena(&parser, "", R"([a, b, c, d, {foo: bar, money: pennys}])", &tree); CHECK(mem.alloc_size == size_before); CHECK(mem.num_allocs == 3); } mem.check_and_reset(); // restore defaults. ryml::set_callbacks(defaults); } //----------------------------------------------------------------------------- /** @addtogroup doc_sample_helpers * @{ */ /** an example for a per-tree memory allocator */ struct PerTreeMemoryExample { std::vector memory_pool = std::vector(10u * 1024u); // 10KB size_t num_allocs = 0, alloc_size = 0; size_t num_deallocs = 0, dealloc_size = 0; ryml::Callbacks callbacks() const { // Above we used static functions to bridge to our methods. // To show a different approach, we employ lambdas here. // Note that there can be no captures in the lambdas // because these are C-style function pointers. ryml::Callbacks cb; cb.m_user_data = (void*) this; cb.m_allocate = [](size_t len, void *, void *data){ return ((PerTreeMemoryExample*) data)->allocate(len); }; cb.m_free = [](void *mem, size_t len, void *data){ return ((PerTreeMemoryExample*) data)->free(mem, len); }; return cb; } void *allocate(size_t len) { void *ptr = &memory_pool[alloc_size]; alloc_size += len; ++num_allocs; if(C4_UNLIKELY(alloc_size > memory_pool.size())) { std::cerr << "out of memory! requested=" << alloc_size << " vs " << memory_pool.size() << " available" << std::endl; std::abort(); } return ptr; } void free(void *mem, size_t len) { CHECK((char*)mem >= &memory_pool.front() && (char*)mem < &memory_pool.back()); CHECK((char*)mem+len >= &memory_pool.front() && (char*)mem+len <= &memory_pool.back()); dealloc_size += len; ++num_deallocs; // no need to free here } // checking ~PerTreeMemoryExample() { check_and_reset(); } void check_and_reset() { std::cout << "size: alloc=" << alloc_size << " dealloc=" << dealloc_size << std::endl; std::cout << "count: #allocs=" << num_allocs << " #deallocs=" << num_deallocs << std::endl; CHECK(num_allocs == num_deallocs); CHECK(alloc_size >= dealloc_size); // failure here means a double free CHECK(alloc_size == dealloc_size); // failure here means a leak num_allocs = 0; num_deallocs = 0; alloc_size = 0; dealloc_size = 0; } }; /** @} */ void sample_per_tree_allocator() { PerTreeMemoryExample mrp; PerTreeMemoryExample mr1; PerTreeMemoryExample mr2; // the trees will use the memory in the resources above, // with each tree using a separate resource { // Watchout: ensure that the lifetime of the callbacks target // exceeds the lifetime of the tree. ryml::EventHandlerTree evt_handler(mrp.callbacks()); ryml::Parser parser(&evt_handler); ryml::Tree tree1(mr1.callbacks()); ryml::Tree tree2(mr2.callbacks()); ryml::csubstr yml1 = "{a: b}"; ryml::csubstr yml2 = "{c: d, e: f, g: [h, i, 0, 1, 2, 3]}"; parse_in_arena(&parser, "file1.yml", yml1, &tree1); parse_in_arena(&parser, "file2.yml", yml2, &tree2); } CHECK(mrp.num_allocs == 0); // YAML depth not large enough to warrant a parser allocation CHECK(mr1.alloc_size <= mr2.alloc_size); // because yml2 has more nodes } //----------------------------------------------------------------------------- /** shows how to work around the static initialization order fiasco * when using a static-duration ryml tree * @see https://en.cppreference.com/w/cpp/language/siof */ void sample_static_trees() { // Static trees may incur a static initialization order // problem. This happens because a default-constructed tree will // obtain the callbacks from the current global setting, which may // not have been initialized due to undefined static // initialization order: // // ERROR! depends on ryml::get_callbacks() which may not have been initialized. //static ryml::Tree tree; // // To work around the issue, declare static callbacks // to explicitly initialize the static tree: static ryml::Callbacks callbacks = {}; // use default callback members static ryml::Tree tree(callbacks); // OK // now you can use the tree as normal: ryml::parse_in_arena(R"(doe: "a deer, a female deer")", &tree); CHECK(tree["doe"].val() == "a deer, a female deer"); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** demonstrates how to obtain the (zero-based) location of a node * from a recently parsed tree */ void sample_location_tracking() { // NOTE: locations are zero-based. If you intend to show the // location to a human user, you may want to pre-increment the line // and column by 1. ryml::csubstr yaml = R"({ aa: contents, foo: [one, [two, three]] })"; // A parser is needed to track locations, and it has to be // explicitly set to do it. Location tracking is disabled by // default. ryml::ParserOptions opts = {}; opts.locations(true); // enable locations, default is false ryml::EventHandlerTree evt_handler = {}; ryml::Parser parser(&evt_handler, opts); CHECK(parser.options().locations()); // When locations are enabled, the first task while parsing will // consist of building and caching (in the parser) a // source-to-node lookup structure to accelerate location lookups. // // The cost of building the location accelerator is linear in the // size of the source buffer. This increased cost is the reason // for the opt-in requirement. When locations are disabled there // is no cost. // // Building the location accelerator may trigger an allocation, // but this can and should be avoided by reserving prior to // parsing: parser.reserve_locations(50u); // reserve for 50 lines // Now the structure will be built during parsing: ryml::Tree tree = parse_in_arena(&parser, "source.yml", yaml); // After this, we are ready to query the location from the parser: ryml::Location loc = tree.rootref().location(parser); // As for the complexity of the query: for large buffers it is // O(log(numlines)). For short source buffers (30 lines and less), // it is O(numlines), as a plain linear search is faster in this // case. CHECK(parser.location_contents(loc).begins_with("{")); CHECK(loc.offset == 0u); CHECK(loc.line == 0u); CHECK(loc.col == 0u); // on the next call, we only pay O(log(numlines)) because the // rebuild is already available: loc = tree["aa"].location(parser); CHECK(parser.location_contents(loc).begins_with("aa")); CHECK(loc.offset == 2u); CHECK(loc.line == 1u); CHECK(loc.col == 0u); // KEYSEQ in flow style: points at the key loc = tree["foo"].location(parser); CHECK(parser.location_contents(loc).begins_with("foo")); CHECK(loc.offset == 16u); CHECK(loc.line == 2u); CHECK(loc.col == 0u); loc = tree["foo"][0].location(parser); CHECK(parser.location_contents(loc).begins_with("one")); CHECK(loc.line == 2u); CHECK(loc.col == 6u); // SEQ in flow style: location points at the opening '[' (there's no key) loc = tree["foo"][1].location(parser); CHECK(parser.location_contents(loc).begins_with("[")); CHECK(loc.line == 2u); CHECK(loc.col == 11u); loc = tree["foo"][1][0].location(parser); CHECK(parser.location_contents(loc).begins_with("two")); CHECK(loc.line == 2u); CHECK(loc.col == 12u); loc = tree["foo"][1][1].location(parser); CHECK(parser.location_contents(loc).begins_with("three")); CHECK(loc.line == 2u); CHECK(loc.col == 17u); // NOTE. The parser locations always point at the latest buffer to // be parsed with the parser object, so they must be queried using // the corresponding latest tree to be parsed. This means that if // the parser is reused, earlier trees will loose the possibility // of querying for location. It is undefined behavior to query the // parser for the location of a node from an earlier tree: ryml::Tree docval = parse_in_arena(&parser, "docval.yaml", "this is a docval"); // From now on, none of the locations from the previous tree can // be queried: //loc = tree.rootref().location(parser); // ERROR, undefined behavior loc = docval.rootref().location(parser); // OK. this is the latest tree from this parser CHECK(parser.location_contents(loc).begins_with("this is a docval")); CHECK(loc.line == 0u); CHECK(loc.col == 0u); // NOTES ABOUT CONTAINER LOCATIONS ryml::Tree tree2 = parse_in_arena(&parser, "containers.yaml", R"( a new: buffer to: be parsed map with key: first: value second: value seq with key: - first value - second value - - nested first value - nested second value - nested first: value nested second: value )"); // (Likewise, the docval tree can no longer be used to query.) // // For key-less block-style maps, the location of the container // points at the first child's key. For example, in this case // the root does not have a key, so its location is taken // to be at the first child: loc = tree2.rootref().location(parser); CHECK(parser.location_contents(loc).begins_with("a new")); CHECK(loc.offset == 1u); CHECK(loc.line == 1u); CHECK(loc.col == 0u); // note the first child points exactly at the same place: loc = tree2["a new"].location(parser); CHECK(parser.location_contents(loc).begins_with("a new")); CHECK(loc.offset == 1u); CHECK(loc.line == 1u); CHECK(loc.col == 0u); loc = tree2["to"].location(parser); CHECK(parser.location_contents(loc).begins_with("to")); CHECK(loc.line == 2u); CHECK(loc.col == 0u); // but of course, if the block-style map is a KEYMAP, then the // location is the map's key, and not the first child's key: loc = tree2["map with key"].location(parser); CHECK(parser.location_contents(loc).begins_with("map with key")); CHECK(loc.line == 3u); CHECK(loc.col == 0u); loc = tree2["map with key"]["first"].location(parser); CHECK(parser.location_contents(loc).begins_with("first")); CHECK(loc.line == 4u); CHECK(loc.col == 2u); loc = tree2["map with key"]["second"].location(parser); CHECK(parser.location_contents(loc).begins_with("second")); CHECK(loc.line == 5u); CHECK(loc.col == 2u); // same thing for KEYSEQ: loc = tree2["seq with key"].location(parser); CHECK(parser.location_contents(loc).begins_with("seq with key")); CHECK(loc.line == 6u); CHECK(loc.col == 0u); loc = tree2["seq with key"][0].location(parser); CHECK(parser.location_contents(loc).begins_with("first value")); CHECK(loc.line == 7u); CHECK(loc.col == 4u); loc = tree2["seq with key"][1].location(parser); CHECK(parser.location_contents(loc).begins_with("second value")); CHECK(loc.line == 8u); CHECK(loc.col == 4u); // SEQ nested in SEQ: container location points at the first child's "- " dash loc = tree2["seq with key"][2].location(parser); CHECK(parser.location_contents(loc).begins_with("- nested first value")); CHECK(loc.line == 10u); CHECK(loc.col == 4u); loc = tree2["seq with key"][2][0].location(parser); CHECK(parser.location_contents(loc).begins_with("nested first value")); CHECK(loc.line == 10u); CHECK(loc.col == 6u); // MAP nested in SEQ: same as above: point to key loc = tree2["seq with key"][3].location(parser); CHECK(parser.location_contents(loc).begins_with("nested first: ")); CHECK(loc.line == 13u); CHECK(loc.col == 4u); loc = tree2["seq with key"][3][0].location(parser); CHECK(parser.location_contents(loc).begins_with("nested first: ")); CHECK(loc.line == 13u); CHECK(loc.col == 4u); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- /** @addtogroup doc_sample_helpers * @{ */ namespace /*anon*/ { static int num_checks = 0; static int num_failed_checks = 0; } // namespace /*anon*/ bool report_check(int line, const char *predicate, bool result) { ++num_checks; const char *msg = predicate ? "OK! " : "OK!"; if(!result) { ++num_failed_checks; msg = predicate ? "FAIL: " : "FAIL"; } std::cout << __FILE__ << ':' << line << ": " << msg << (predicate ? predicate : "") << std::endl; return result; } int report_checks() { std::cout << "Completed " << num_checks << " checks." << std::endl; if(num_failed_checks) std::cout << "ERROR: " << num_failed_checks << '/' << num_checks << " checks failed." << std::endl; else std::cout << "SUCCESS!" << std::endl; return num_failed_checks; } // methods for the example error handler #ifndef C4_EXCEPTIONS /*environment for setjmp*/ static std::jmp_buf s_jmp_env; static std::string s_jmp_msg; #endif /** checking that an assertion occurs while calling fn. assertions are * enabled if @ref RYML_USE_ASSERT is defined. * @ingroup doc_sample_helpers */ template bool ErrorHandlerExample::check_assertion_occurs(Fn &&fn) { #if RYML_USE_ASSERT return check_error_occurs(std::forward(fn)); #else (void)fn; // do nothing otherwise, as there would be undefined behavior return true; #endif } /** checking that an error occurs while calling fn * @ingroup doc_sample_helpers */ template bool ErrorHandlerExample::check_error_occurs(Fn &&fn) { saved_msg_short.clear(); saved_msg_full.clear(); saved_msg_full_with_context.clear(); saved_basic_loc = {}; saved_parse_loc = {}; saved_visit_tree = {}; saved_visit_id = ryml::NONE; bool got_error = false; #ifdef C4_EXCEPTIONS try { std::forward(fn)(); } catch(std::exception const&) { got_error = true; } #else if(setjmp(s_jmp_env) == 0) { std::forward(fn)(); } else { got_error = true; } #endif return got_error; } /** interrupt execution * @ingroup doc_sample_helpers */ [[noreturn]] void stopexec(std::string const& s) { #ifdef C4_EXCEPTIONS throw std::runtime_error(s); #else s_jmp_msg = s; std::longjmp(s_jmp_env, 1); // jump to the corresponding call to setjmp(). #endif } /** this is where the callback implementation goes. Remember that it must not return. * @ingroup doc_sample_helpers * */ [[noreturn]] void ErrorHandlerExample::on_error_basic(ryml::csubstr msg, ryml::ErrorDataBasic const& errdata) { saved_msg_short.assign(msg.str, msg.len); // build a full error message with location ryml::err_basic_format([this](ryml::csubstr s){ saved_msg_full.append(s.str, s.len); }, msg, errdata); saved_msg_full_with_context = saved_msg_full; saved_basic_loc = errdata.location; stopexec(saved_msg_short); } /** this is where the callback implementation goes. Remember that it must not return. * @ingroup doc_sample_helpers * @see ryml::format_location_context * */ [[noreturn]] void ErrorHandlerExample::on_error_parse(ryml::csubstr msg, ryml::ErrorDataParse const& errdata) { saved_msg_short.assign(msg.str, msg.len); // build a full error message with location ryml::err_parse_format([this](ryml::csubstr s){ saved_msg_full.append(s.str, s.len); }, msg, errdata); // To add the source context, the source buffer is required. If // the caller is interested in enriching the full message with the // source buffer context, he can ensure that the source buffer is // kept, and then arrange the handler to access it. // For now, we assign the full message without context: saved_msg_full_with_context = saved_msg_full; saved_basic_loc = errdata.cpploc; saved_parse_loc = errdata.ymlloc; stopexec(saved_msg_full); } /** this is where the callback implementation goes. Remember that it must not return. * @ingroup doc_sample_helpers * */ [[noreturn]] void ErrorHandlerExample::on_error_visit(ryml::csubstr msg, ryml::ErrorDataVisit const& errdata) { saved_msg_short.assign(msg.str, msg.len); // build a full error message with location ryml::err_visit_format([this](ryml::csubstr s){ saved_msg_full.append(s.str, s.len); }, msg, errdata); // To add the source context, the source buffer is required. If // the caller is interested in enriching the full message with the // source buffer context, he can ensure that the source buffer is // kept, and then arrange the handler to access it. // For now, we assign the full message without context: saved_msg_full_with_context = saved_msg_full; saved_basic_loc = errdata.cpploc; saved_visit_tree = errdata.tree; saved_visit_id = errdata.node; stopexec(saved_msg_full); } /** trampoline function to call the object's method */ [[noreturn]] void ErrorHandlerExample::s_error_basic(ryml::csubstr msg, ryml::ErrorDataBasic const& errdata, void *this_) { static_cast(this_)->on_error_basic(msg, errdata); } /** trampoline function to call the object's method */ [[noreturn]] void ErrorHandlerExample::s_error_parse(ryml::csubstr msg, ryml::ErrorDataParse const& errdata, void *this_) { static_cast(this_)->on_error_parse(msg, errdata); } /** trampoline function to call the object's method */ [[noreturn]] void ErrorHandlerExample::s_error_visit(ryml::csubstr msg, ryml::ErrorDataVisit const& errdata, void *this_) { static_cast(this_)->on_error_visit(msg, errdata); } /** a helper to create the Callbacks object for the custom error handler * @ingroup doc_sample_helpers * */ ryml::Callbacks ErrorHandlerExample::callbacks() { return ryml::Callbacks{} .set_user_data(this) .set_error_basic(&ErrorHandlerExample::s_error_basic) .set_error_parse(&ErrorHandlerExample::s_error_parse) .set_error_visit(&ErrorHandlerExample::s_error_visit); } /** test that this handler is currently set */ void ErrorHandlerExample::check_enabled() const { ryml::Callbacks const& current = ryml::get_callbacks(); CHECK(current.m_error_basic == &s_error_basic); CHECK(current.m_error_parse == &s_error_parse); CHECK(current.m_error_visit == &s_error_visit); CHECK(current.m_allocate == defaults.m_allocate); CHECK(current.m_free == defaults.m_free); } /** test that this handler is currently not set */ void ErrorHandlerExample::check_disabled() const { ryml::Callbacks const& current = ryml::get_callbacks(); CHECK(current.m_error_basic != &s_error_basic); CHECK(current.m_error_parse != &s_error_parse); CHECK(current.m_error_visit != &s_error_visit); CHECK(current.m_allocate == defaults.m_allocate); CHECK(current.m_free == defaults.m_free); } // helper functions for sample_parse_file() C4_SUPPRESS_WARNING_MSVC_WITH_PUSH(4996) // fopen: this function may be unsafe /** load a file from disk into an existing CharContainer */ template size_t file_get_contents(const char *filename, CharContainer *v) { std::FILE *fp = std::fopen(filename, "rb"); // NOLINT if(fp == nullptr) _RYML_ERR_BASIC("{}: could not open file", filename); std::fseek(fp, 0, SEEK_END); // NOLINT long sz = std::ftell(fp); // NOLINT v->resize(static_cast(sz)); if(sz) { std::rewind(fp); // NOLINT size_t ret = std::fread(&(*v)[0], 1, v->size(), fp); if(ret != (size_t)sz) _RYML_ERR_BASIC("{}: failed to read: expect {}B, got {}B", filename, sz, ret); } std::fclose(fp); // NOLINT return v->size(); } /** load a file from disk and return a newly created CharContainer */ template CharContainer file_get_contents(const char *filename) { CharContainer cc; file_get_contents(filename, &cc); return cc; } /** save a buffer into a file */ template void file_put_contents(const char *filename, CharContainer const& v, const char* access) { file_put_contents(filename, v.empty() ? "" : &v[0], v.size(), access); } /** save a buffer into a file */ void file_put_contents(const char *filename, const char *buf, size_t sz, const char* access) { std::FILE *fp = std::fopen(filename, access); if(fp == nullptr) _RYML_ERR_BASIC("{}: could not open file", filename); std::fwrite(buf, 1, sz, fp); // NOLINT std::fclose(fp); // NOLINT } C4_SUPPRESS_WARNING_MSVC_POP /** @} */ // doc_sample_helpers /** @} */ // doc_quickstart C4_SUPPRESS_WARNING_GCC_CLANG_POP