Files
rapidyaml/samples/quickstart.cpp
2026-01-02 19:02:39 +00:00

6388 lines
251 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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 $<TARGET_FILE:ryml-quickstart>
* DEPENDS ryml-quickstart
* COMMENT "running: $<TARGET_FILE:ryml-quickstart>")
* ```
* 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 <ryml_all.hpp>
#elif defined(RYML_SINGLE_HEADER_LIB) // using the single header from a library
#include <ryml_all.hpp>
#else
#include <ryml.hpp>
// <ryml_std.hpp> 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 <ryml_std.hpp> // optional header, provided for std:: interop
#include <c4/format.hpp> // needed for the examples below
// optional header, definitions for error utilities to implement
// error callbacks:
#include <c4/yml/error.def.hpp>
#endif
// these are needed for the examples below
#include <iostream>
#include <sstream>
#include <vector>
#include <map>
#ifdef C4_EXCEPTIONS
#include <stdexcept>
#else
#include <csetjmp>
#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<class Fn> bool check_error_occurs(Fn &&fn);
template<class Fn> 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<class CharContainer> CharContainer file_get_contents(const char *filename);
template<class CharContainer> size_t file_get_contents(const char *filename, CharContainer *v);
template<class CharContainer> 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<std::string>(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<std::string>(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<csubstr,id_type>`
// 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<std::string>(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 <ryml/std/string.hpp> header
// or of the umbrella header <ryml_std.hpp>.
//
// not including <string> 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 <ryml/std/string.hpp>
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<decltype(foobar_str_ro)>::value, "this is an array");
static_assert(std::is_array<decltype(foobar_str_rw)>::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<decltype(foobar_str_ro)>::value, "this is a decayed pointer");
static_assert(!std::is_array<decltype(foobar_str_rw)>::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<std::string>(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<char> instead:
{
std::vector<char> contents = file_get_contents<std::vector<char>>(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<std::string>(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<std::string>(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<std::string>(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<std::string>(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<std::string>(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<std::string>(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<std::string>(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<std::string>(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<std::string>(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<std::string>(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<std::string>(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<std::string>(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<ryml::csubstr> 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<std::string>(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<float >::quiet_NaN();
const double dnan = std::numeric_limits<double>::quiet_NaN();
const float finf = std::numeric_limits<float >::infinity();
const double dinf = std::numeric_limits<double>::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<uint8_t>(t["val"].val()));
CHECK(ryml::overflows<int8_t>(t["val"].val()));
CHECK( ! ryml::overflows<int16_t>(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<std::string>(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<std::string>(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<std::string>(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<std::string>(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).
// <ryml_std.hpp> has to_substr() definitions for std::string and
// std::vector<char>.
//
// 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<char> 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).
// <ryml_std.hpp> has to_substr() definitions for std::string and
// std::vector<char>.
//
// 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<char> 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).
// <ryml_std.hpp> has to_substr() definitions for std::string and
// std::vector<char>.
//
// 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<char> 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<std::string>(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<char>`: @ref ext/c4core/src/c4/std/vector.hpp
* - container types:
* - for `std::vector<T>`: @ref src/c4/yml/std/vector.hpp
* - for `std::map<K,V>`: @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<class T> struct vec2 { T x, y; }; ///< example scalar type, serialized and deserialized
template<class T> struct vec3 { T x, y, z; }; ///< example scalar type, serialized and deserialized
template<class T> struct vec4 { T x, y, z, w; }; ///< example scalar type, serialized and deserialized
template<class T> struct parse_only_vec2 { T x, y; }; ///< example scalar type, deserialized only
template<class T> struct parse_only_vec3 { T x, y, z; }; ///< example scalar type, deserialized only
template<class T> struct parse_only_vec4 { T x, y, z, w; }; ///< example scalar type, deserialized only
template<class T> struct emit_only_vec2 { T x, y; }; ///< example scalar type, serialized only
template<class T> struct emit_only_vec3 { T x, y, z; }; ///< example scalar type, serialized only
template<class T> 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<class T> size_t to_chars(ryml::substr buf, vec2<T> v) { return ryml::format(buf, "({},{})", v.x, v.y); }
template<class T> size_t to_chars(ryml::substr buf, vec3<T> v) { return ryml::format(buf, "({},{},{})", v.x, v.y, v.z); }
template<class T> size_t to_chars(ryml::substr buf, vec4<T> v) { return ryml::format(buf, "({},{},{},{})", v.x, v.y, v.z, v.w); }
template<class T> size_t to_chars(ryml::substr buf, emit_only_vec2<T> v) { return ryml::format(buf, "({},{})", v.x, v.y); }
template<class T> size_t to_chars(ryml::substr buf, emit_only_vec3<T> v) { return ryml::format(buf, "({},{},{})", v.x, v.y, v.z); }
template<class T> size_t to_chars(ryml::substr buf, emit_only_vec4<T> v) { return ryml::format(buf, "({},{},{},{})", v.x, v.y, v.z, v.w); }
/** @} */
/** @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<class T> bool from_chars(ryml::csubstr buf, vec2<T> *v) { size_t ret = ryml::unformat(buf, "({},{})", v->x, v->y); return ret != ryml::yml::npos; }
template<class T> bool from_chars(ryml::csubstr buf, vec3<T> *v) { size_t ret = ryml::unformat(buf, "({},{},{})", v->x, v->y, v->z); return ret != ryml::yml::npos; }
template<class T> bool from_chars(ryml::csubstr buf, vec4<T> *v) { size_t ret = ryml::unformat(buf, "({},{},{},{})", v->x, v->y, v->z, v->w); return ret != ryml::yml::npos; }
template<class T> bool from_chars(ryml::csubstr buf, parse_only_vec2<T> *v) { size_t ret = ryml::unformat(buf, "({},{})", v->x, v->y); return ret != ryml::yml::npos; }
template<class T> bool from_chars(ryml::csubstr buf, parse_only_vec3<T> *v) { size_t ret = ryml::unformat(buf, "({},{},{})", v->x, v->y, v->z); return ret != ryml::yml::npos; }
template<class T> bool from_chars(ryml::csubstr buf, parse_only_vec4<T> *v) { size_t ret = ryml::unformat(buf, "({},{},{},{})", v->x, v->y, v->z, v->w); return ret != ryml::yml::npos; }
/** @} */ // 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<int> v2in{10, 11};
vec2<int> 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<int> v3in{100, 101, 102};
vec3<int> 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<int> v4in{1000, 1001, 1002, 1003};
vec4<int> 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<std::string>(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<int> eov2in{20, 21}; // only has to_chars()
parse_only_vec2<int> 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<int> eov3in{30, 31, 32}; // only has to_chars()
parse_only_vec3<int> 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<int> eov4in{40, 41, 42, 43}; // only has to_chars()
parse_only_vec4<int> 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<std::string>(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<class T>
struct my_seq_type
{
std::vector<T> seq_member;
};
/** example user container type: map-like */
template<class K, class V>
struct my_map_type
{
std::map<K, V> 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<int> v2;
vec3<int> v3;
vec4<int> v4;
// these are container nodes:
my_seq_type<int> seq;
my_map_type<int, int> map;
};
template<class T>
void write(ryml::NodeRef *n, my_seq_type<T> const& seq)
{
*n |= ryml::SEQ;
for(auto const& v : seq.seq_member)
n->append_child() << v;
}
template<class K, class V>
void write(ryml::NodeRef *n, my_map_type<K, V> 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<class T>
bool read(ryml::ConstNodeRef const& n, my_seq_type<T> *seq)
{
seq->seq_member.resize(static_cast<size_t>(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<class K, class V>
bool read(ryml::ConstNodeRef const& n, my_map_type<K, V> *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<std::string>(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<my_type> vmt;
tree.rootref() >> vmt;
CHECK(vmt.size() == 3);
ryml::Tree tree_out;
tree_out.rootref() << vmt;
CHECK(ryml::emitrs_yaml<std::string>(tree_out) == yml_std_string);
}
//-----------------------------------------------------------------------------
/** control precision of serialized floats */
void sample_float_precision()
{
std::vector<double> 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<double> 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<std::string>(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<std::string>(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<std::string>(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<char>
{
// 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<char> 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<char> another = ryml::emitrs_yaml<std::vector<char>>(treeb);
CHECK(ryml::to_csubstr(another) == ymlb);
// you can also emit nested nodes:
another = ryml::emitrs_yaml<std::vector<char>>(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<std::string>(treeb);
CHECK(ryml::to_csubstr(another) == ymlb);
// you can also emit nested nodes:
another = ryml::emitrs_yaml<std::string>(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<std::string>(tree[3]["beer"]) == R"(beer:
- Rochefort 10
- Busch
- Leffe Rituel
- - and so
- many other
- wonderful beers
)");
CHECK(ryml::emitrs_yaml<std::string>(tree[3]["beer"][0]) == "Rochefort 10");
CHECK(ryml::emitrs_yaml<std::string>(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<std::string>(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<std::string>(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<std::string>(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<std::string>(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<std::string>(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<std::string>(tree) == json);
CHECK(ryml::emitrs_json<std::string>(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<std::string>(json_tree);
CHECK(ryml::emitrs_yaml<std::string>(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<std::string>(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<std::string>(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<std::string>(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<std::string>(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<std::string>(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<std::string>(t) == R"(orig: &orig {foo: bar,baz: bat}
copy: {<<: *orig}
notcopy: {test: *orig,<<: *orig}
notref: {<<: '*orig'}
)");
t.resolve();
CHECK(ryml::emitrs_yaml<std::string>(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<std::string>(t) == R"(orig1: &orig1 {foo: bar}
orig2: &orig2 {baz: bat}
orig3: &orig3 {and: more}
copy: {<<: [*orig1,*orig2,*orig3]}
)");
t.resolve();
CHECK(ryml::emitrs_yaml<std::string>(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) == "<tag:yaml.org,2002:map>");
CHECK(ryml::from_tag_long(ryml::TAG_SEQ) == "<tag:yaml.org,2002:seq>");
CHECK(ryml::from_tag_long(ryml::TAG_STR) == "<tag:yaml.org,2002:str>");
CHECK(ryml::from_tag_long(ryml::TAG_INT) == "<tag:yaml.org,2002:int>");
CHECK(ryml::from_tag_long(ryml::TAG_SET) == "<tag:yaml.org,2002:set>");
// and likewise:
CHECK(ryml::to_tag("!map") == ryml::TAG_NONE);
CHECK(ryml::to_tag("<tag:yaml.org,2002:map>") == ryml::TAG_MAP);
CHECK(ryml::to_tag("<tag:yaml.org,2002:seq>") == ryml::TAG_SEQ);
CHECK(ryml::to_tag("<tag:yaml.org,2002:str>") == ryml::TAG_STR);
CHECK(ryml::to_tag("<tag:yaml.org,2002:int>") == ryml::TAG_INT);
CHECK(ryml::to_tag("<tag:yaml.org,2002:set>") == ryml::TAG_SET);
// to normalize a tag as much as possible, use normalize_tag():
CHECK(ryml::normalize_tag("!!map") == "!!map");
CHECK(ryml::normalize_tag("!<tag:yaml.org,2002:map>") == "!!map");
CHECK(ryml::normalize_tag("<tag:yaml.org,2002:map>") == "!!map");
CHECK(ryml::normalize_tag("tag:yaml.org,2002:map") == "!!map");
CHECK(ryml::normalize_tag("!<!!map>") == "<!!map>");
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") == "<tag:yaml.org,2002:map>");
CHECK(ryml::normalize_tag_long("!<tag:yaml.org,2002:map>") == "<tag:yaml.org,2002:map>");
CHECK(ryml::normalize_tag_long("<tag:yaml.org,2002:map>") == "<tag:yaml.org,2002:map>");
CHECK(ryml::normalize_tag_long("tag:yaml.org,2002:map") == "<tag:yaml.org,2002:map>");
CHECK(ryml::normalize_tag_long("!<!!map>") == "<!!map>");
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<std::string>(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<std::string>(normalized_tree_long) == R"(--- !<tag:yaml.org,2002:map>
a: 0
b: 1
--- !map
a: b
--- !<tag:yaml.org,2002:seq>
- a
- b
--- !<tag:yaml.org,2002:str> a b
--- !<tag:yaml.org,2002:str> 'a: b'
---
!<tag:yaml.org,2002:str> a: b
--- !<tag:yaml.org,2002:set>
a:
b:
--- !<tag:yaml.org,2002:set>
a:
--- !<tag:yaml.org,2002:seq>
- !<tag:yaml.org,2002:int> 0
- !<tag:yaml.org,2002:str> 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<std::string>(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<std::string>(tree) == R"(%TAG !m! !my-
--- !<!my-light> fluorescent
...
%TAG !m! !meta-
--- !<!meta-light> 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<std::string>(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<std::string>(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<std::string>(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<std::string>(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<char> memory_pool = std::vector<char>(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<char> memory_pool = std::vector<char>(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<class Fn>
bool ErrorHandlerExample::check_assertion_occurs(Fn &&fn)
{
#if RYML_USE_ASSERT
return check_error_occurs(std::forward<Fn>(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<class Fn>
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>(fn)();
}
catch(std::exception const&)
{
got_error = true;
}
#else
if(setjmp(s_jmp_env) == 0)
{
std::forward<Fn>(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<ErrorHandlerExample*>(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<ErrorHandlerExample*>(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<ErrorHandlerExample*>(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<class CharContainer>
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<typename CharContainer::size_type>(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<class CharContainer>
CharContainer file_get_contents(const char *filename)
{
CharContainer cc;
file_get_contents(filename, &cc);
return cc;
}
/** save a buffer into a file */
template<class CharContainer>
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