rapidyaml  0.16.0
parseandemitYAML,anddoitfast
Serialization

Functions

void sample_fundamental_types ()
 serialize/deserialize fundamental types More...
 
void sample_empty_null_values ()
 serialize/deserialize/query empty or null values More...
 
void sample_formatting ()
 control formatting when serializing/deserializing More...
 
void sample_base64 ()
 encode/decode base64 More...
 
void sample_serialize_basic ()
 serialize/deserialize fundamental types More...
 
void sample_user_scalar_types ()
 serialize/deserialize scalar (leaf/scalar) types More...
 
void sample_user_container_types_brief ()
 serialize/deserialize container (map or seq) types: brief version More...
 
void sample_user_container_types ()
 serialize/deserialize container (map or seq) types More...
 
void sample_std_types ()
 serialize/deserialize STL containers More...
 
void sample_deserialize_error ()
 shows error on deserializing nested nodes More...
 
void sample_float_precision ()
 control precision of serialized floats More...
 

Detailed Description

Function Documentation

void sample_fundamental_types ( )

serialize/deserialize fundamental types

ryml provides facilities for serializing and deserializing the C++ fundamental types, including boolean and null values; this is provided by the several overloads in to_chars: generalized chars to value and from_chars: generalized chars to value.

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 sample_user_scalar_types for an example on how to achieve this, and see Serialization/deserialization for more information on serialization.

Definition at line 3017 of file quickstart.cpp.

Referenced by main().

3018 {
3019  ryml::Tree tree;
3020  CHECK(tree.arena().empty());
3021  CHECK(tree.to_arena('a') == "a"); CHECK(tree.arena() == "a");
3022  CHECK(tree.to_arena("bcde") == "bcde"); CHECK(tree.arena() == "abcde");
3023  CHECK(tree.to_arena(unsigned(0)) == "0"); CHECK(tree.arena() == "abcde0");
3024  CHECK(tree.to_arena(int(1)) == "1"); CHECK(tree.arena() == "abcde01");
3025  CHECK(tree.to_arena(uint8_t(0)) == "0"); CHECK(tree.arena() == "abcde010");
3026  CHECK(tree.to_arena(uint16_t(1)) == "1"); CHECK(tree.arena() == "abcde0101");
3027  CHECK(tree.to_arena(uint32_t(2)) == "2"); CHECK(tree.arena() == "abcde01012");
3028  CHECK(tree.to_arena(uint64_t(3)) == "3"); CHECK(tree.arena() == "abcde010123");
3029  CHECK(tree.to_arena(int8_t( 4)) == "4"); CHECK(tree.arena() == "abcde0101234");
3030  CHECK(tree.to_arena(int8_t(-4)) == "-4"); CHECK(tree.arena() == "abcde0101234-4");
3031  CHECK(tree.to_arena(int16_t( 5)) == "5"); CHECK(tree.arena() == "abcde0101234-45");
3032  CHECK(tree.to_arena(int16_t(-5)) == "-5"); CHECK(tree.arena() == "abcde0101234-45-5");
3033  CHECK(tree.to_arena(int32_t( 6)) == "6"); CHECK(tree.arena() == "abcde0101234-45-56");
3034  CHECK(tree.to_arena(int32_t(-6)) == "-6"); CHECK(tree.arena() == "abcde0101234-45-56-6");
3035  CHECK(tree.to_arena(int64_t( 7)) == "7"); CHECK(tree.arena() == "abcde0101234-45-56-67");
3036  CHECK(tree.to_arena(int64_t(-7)) == "-7"); CHECK(tree.arena() == "abcde0101234-45-56-67-7");
3037  CHECK(tree.to_arena((void*)1) == "0x1"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x1");
3038  CHECK(tree.to_arena(float(0.124)) == "0.124"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.124");
3039  CHECK(tree.to_arena(double(0.234)) == "0.234"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.234");
3040 
3041  // write boolean values - see also sample_formatting()
3042  CHECK(tree.to_arena(bool(true)) == "1"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.2341");
3043  CHECK(tree.to_arena(bool(false)) == "0"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410");
3044  CHECK(tree.to_arena(c4::fmt::boolalpha(true)) == "true"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410true");
3045  CHECK(tree.to_arena(c4::fmt::boolalpha(false)) == "false"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410truefalse");
3046 
3047  // write special float values
3048  // see also sample_float_precision()
3049  const float fnan = std::numeric_limits<float >::quiet_NaN();
3050  const double dnan = std::numeric_limits<double>::quiet_NaN();
3051  const float finf = std::numeric_limits<float >::infinity();
3052  const double dinf = std::numeric_limits<double>::infinity();
3053  CHECK(tree.to_arena( finf) == ".inf"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410truefalse.inf");
3054  CHECK(tree.to_arena( dinf) == ".inf"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410truefalse.inf.inf");
3055  CHECK(tree.to_arena(-finf) == "-.inf"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410truefalse.inf.inf-.inf");
3056  CHECK(tree.to_arena(-dinf) == "-.inf"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410truefalse.inf.inf-.inf-.inf");
3057  CHECK(tree.to_arena( fnan) == ".nan"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410truefalse.inf.inf-.inf-.inf.nan");
3058  CHECK(tree.to_arena( dnan) == ".nan"); CHECK(tree.arena() == "abcde0101234-45-56-67-70x10.1240.23410truefalse.inf.inf-.inf-.inf.nan.nan");
3059 
3060  // read special float values
3061  // see also sample_float_precision()
3062  C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH("-Wfloat-equal");
3063  tree = ryml::parse_in_arena(R"({ninf: -.inf, pinf: .inf, nan: .nan})");
3064  float f = 0.f;
3065  double d = 0.;
3066  CHECK(f == 0.f);
3067  CHECK(d == 0.);
3068  tree["ninf"].load(&f); CHECK(f == -finf);
3069  tree["ninf"].load(&d); CHECK(d == -dinf);
3070  tree["pinf"].load(&f); CHECK(f == finf);
3071  tree["pinf"].load(&d); CHECK(d == dinf);
3072  tree["nan" ].load(&f); CHECK(std::isnan(f));
3073  tree["nan" ].load(&d); CHECK(std::isnan(d));
3074  C4_SUPPRESS_WARNING_GCC_CLANG_POP
3075 
3076  // value overflow detection:
3077  // (for integral types only)
3078  {
3079  // we will be detecting errors below, so we use this sample helper
3080  ScopedErrorHandlerExample err = {};
3081  ryml::Tree t(err.callbacks()); // instantiate with the error-detecting callbacks
3082  // create a simple tree with an int value
3083  ryml::parse_in_arena(R"({val: 258})", &t);
3084  // by default, overflow is not detected:
3085  uint8_t valu8 = 0;
3086  int8_t vali8 = 0;
3087  ryml::ConstNodeRef n = t["val"];
3088  n.load(&valu8); CHECK(valu8 == 2); // not 257; it wrapped around
3089  n.load(&vali8); CHECK(vali8 == 2); // not 257; it wrapped around
3090  // ...but there are facilities to detect overflow
3091  CHECK(ryml::overflows<uint8_t>(n.val()));
3092  CHECK(ryml::overflows<int8_t>(n.val()));
3093  CHECK( ! ryml::overflows<int16_t>(n.val()));
3094  // and there is a format helper
3095  CHECK(err.check_error_occurs([&]{
3096  n.load(ryml::fmt::overflow_checked(valu8)); // this will cause an error
3097  }));
3098  CHECK(err.check_error_occurs([&]{
3099  n.load(ryml::fmt::overflow_checked(vali8)); // this will cause an error
3100  }));
3101  }
3102 }
ryml::Callbacks callbacks()
a helper to create the Callbacks object for the custom error handler
Shows how to create a scoped error handler.
Definition: quickstart.cpp:368
void load(id_type node, T *v, bool check_readable=true) const
(1) deserialize the node's contents (val or container) to the given variable, forwarding to the user-...
Definition: tree.hpp:871
void load(T *v, bool check_readable=true) const
(1) deserialize the node's contents (val or container) to the given variable, forwarding to the user-...
Definition: node.hpp:345
csubstr arena() const
get the current arena
Definition: tree.hpp:1317
void parse_in_arena(Parser *parser, csubstr filename, csubstr yaml, Tree *tree, id_type node_id)
(1) parse YAML into an existing tree node. The filename will be used in any error messages arising du...
Definition: parse.cpp:209
#define CHECK(predicate)
a testing assertion, used only in this quickstart
Definition: quickstart.cpp:310
Holds a pointer to an existing tree, and a node id.
Definition: node.hpp:736
boolalpha_ boolalpha(T const &val=false)
tag function to mark a variable to be written as an alphabetic boolean, ie as either true or false ...
Definition: format.hpp:70
bool empty() const noexcept
Definition: substr.hpp:356
csubstr val() const RYML_NOEXCEPT
Forward to Tree::val().
Definition: node.hpp:179
bool check_error_occurs(Fn &&fn)
checking that an error occurs while calling fn
csubstr to_arena(T const &a)
serialize the given variable to the tree's arena, growing it as needed to accomodate the serializatio...
Definition: tree.hpp:1349
void sample_empty_null_values ( )

serialize/deserialize/query empty or null values

Shows how to deal with empty/null values.

See also c4::yml::Tree::val_is_null

Definition at line 3109 of file quickstart.cpp.

Referenced by main().

3110 {
3111  // reading empty/null values - see also sample_formatting()
3113  "plain:" "\n"
3114  "squoted: ''" "\n"
3115  "dquoted: \"\"" "\n"
3116  "literal: |" "\n"
3117  "folded: >" "\n"
3118  "all_null: [~, null, Null, NULL]" "\n"
3119  "non_null: [nULL, non_null, non null, null it is not]" "\n"
3120  "");
3121  // first, remember that .has_val() is a structural predicate
3122  // indicating the node is a leaf, and not a container.
3123  CHECK(tree["plain"].has_val()); // has a val, even if it's empty!
3124  CHECK(tree["squoted"].has_val());
3125  CHECK(tree["dquoted"].has_val());
3126  CHECK(tree["literal"].has_val());
3127  CHECK(tree["folded"].has_val());
3128  CHECK( ! tree["all_null"].has_val());
3129  CHECK( ! tree["non_null"].has_val());
3130  // In essence, has_val() is the logical opposite of is_container()
3131  CHECK( ! tree["plain"].is_container());
3132  CHECK( ! tree["squoted"].is_container());
3133  CHECK( ! tree["dquoted"].is_container());
3134  CHECK( ! tree["literal"].is_container());
3135  CHECK( ! tree["folded"].is_container());
3136  CHECK(tree["all_null"].is_container());
3137  CHECK(tree["non_null"].is_container());
3138  //
3139  // Right. How about the contents of each val?
3140  //
3141  // all of these scalars have zero-length:
3142  CHECK(tree["plain"].val().len == 0);
3143  CHECK(tree["squoted"].val().len == 0);
3144  CHECK(tree["dquoted"].val().len == 0);
3145  CHECK(tree["literal"].val().len == 0);
3146  CHECK(tree["folded"].val().len == 0);
3147  // but only the empty scalar has null string:
3148  CHECK(tree["plain"].val().str == nullptr);
3149  CHECK(tree["squoted"].val().str != nullptr);
3150  CHECK(tree["dquoted"].val().str != nullptr);
3151  CHECK(tree["literal"].val().str != nullptr);
3152  CHECK(tree["folded"].val().str != nullptr);
3153  // likewise, scalar comparison to nullptr has the same results:
3154  // (remember that .val() gives you the scalar value, node must
3155  // have a val, ie must be a leaf node, not a container)
3156  CHECK(tree["plain"].val() == nullptr);
3157  CHECK(tree["squoted"].val() != nullptr);
3158  CHECK(tree["dquoted"].val() != nullptr);
3159  CHECK(tree["literal"].val() != nullptr);
3160  CHECK(tree["folded"].val() != nullptr);
3161  // the tree and node classes provide the corresponding predicate
3162  // functions .key_is_null() and .val_is_null().
3163  // (note that these functions have the same preconditions as .val(),
3164  // because they need get the val to look into its contents)
3165  CHECK(tree["plain"].val_is_null());
3166  CHECK( ! tree["squoted"].val_is_null());
3167  CHECK( ! tree["dquoted"].val_is_null());
3168  CHECK( ! tree["literal"].val_is_null());
3169  CHECK( ! tree["folded"].val_is_null());
3170  // matching to null is case-sensitive. only the cases shown here
3171  // match to null:
3172  for(ryml::ConstNodeRef child : tree["all_null"].children())
3173  {
3174  CHECK(child.val() != nullptr); // it is pointing at a string, so it is not nullptr!
3175  CHECK(child.val_is_null());
3176  }
3177  for(ryml::ConstNodeRef child : tree["non_null"].children())
3178  {
3179  CHECK(child.val() != nullptr);
3180  CHECK( ! child.val_is_null());
3181  }
3182  //
3183  //
3184  // Because the meaning of null/~/empty will vary from application
3185  // to application, ryml makes no assumption on what should be
3186  // serialized as null. It leaves this decision to the user. But
3187  // it also provides the proper toolbox for the user to implement
3188  // its intended solution.
3189  //
3190  // writing/disambiguating null values:
3191  ryml::csubstr null = {};
3192  ryml::csubstr nonnull = "";
3193  ryml::csubstr strnull = "null";
3194  ryml::csubstr tilde = "~";
3195  CHECK(null .len == 0); CHECK(null .str == nullptr); CHECK(null == nullptr);
3196  CHECK(nonnull.len == 0); CHECK(nonnull.str != nullptr); CHECK(nonnull != nullptr);
3197  CHECK(strnull.len != 0); CHECK(strnull.str != nullptr); CHECK(strnull != nullptr);
3198  CHECK(tilde .len != 0); CHECK(tilde .str != nullptr); CHECK(tilde != nullptr);
3199  tree.clear();
3200  tree.clear_arena();
3201  tree.rootref().set_map();
3202  // serializes as an empty plain scalar:
3203  tree["empty_null"].set_serialized(null); CHECK(tree.arena() == "");
3204  // serializes as an empty quoted scalar:
3205  tree["empty_nonnull"].set_serialized(nonnull); CHECK(tree.arena() == "");
3206  // serializes as the normal 'null' string:
3207  tree["str_null"].set_serialized(strnull); CHECK(tree.arena() == "null");
3208  // serializes as the normal '~' string:
3209  tree["str_tilde"].set_serialized(tilde); CHECK(tree.arena() == "null~");
3210  // this is the resulting yaml:
3211  CHECK(ryml::emitrs_yaml<std::string>(tree) == ""
3212  "empty_null: " "\n"
3213  "empty_nonnull: ''" "\n"
3214  "str_null: null" "\n"
3215  "str_tilde: ~" "\n"
3216  "");
3217  // To enforce a particular concept of what is a null string, you
3218  // can use the appropriate condition based on pointer nullity or
3219  // other appropriate criteria.
3220  //
3221  // As an example, proper comparison to nullptr:
3222  auto null_if_nullptr = [](ryml::csubstr s) {
3223  return s.str == nullptr ? "null" : s;
3224  };
3225  tree["empty_null"].set_serialized(null_if_nullptr(null));
3226  tree["empty_nonnull"].set_serialized(null_if_nullptr(nonnull));
3227  tree["str_null"].set_serialized(null_if_nullptr(strnull));
3228  tree["str_tilde"].set_serialized(null_if_nullptr(tilde));
3229  // this is the resulting yaml:
3230  CHECK(ryml::emitrs_yaml<std::string>(tree) == ""
3231  "empty_null: null" "\n"
3232  "empty_nonnull: ''" "\n"
3233  "str_null: null" "\n"
3234  "str_tilde: ~" "\n"
3235  "");
3236  //
3237  // As another example, nullity check based on the YAML nullity
3238  // predicate:
3239  auto null_if_predicate = [](ryml::csubstr s) {
3240  return ryml::scalar_is_null(s) ? "null" : s;
3241  };
3242  tree["empty_null"].set_serialized(null_if_predicate(null));
3243  tree["empty_nonnull"].set_serialized(null_if_predicate(nonnull));
3244  tree["str_null"].set_serialized(null_if_predicate(strnull));
3245  tree["str_tilde"].set_serialized(null_if_predicate(tilde));
3246  // this is the resulting yaml:
3247  CHECK(ryml::emitrs_yaml<std::string>(tree) == ""
3248  "empty_null: null" "\n"
3249  "empty_nonnull: ''" "\n"
3250  "str_null: null" "\n"
3251  "str_tilde: null" "\n"
3252  "");
3253  //
3254  // As another example, nullity check based on the YAML nullity
3255  // predicate, but returning "~" to simbolize nullity:
3256  auto tilde_if_predicate = [](ryml::csubstr s) {
3257  return ryml::scalar_is_null(s) ? "~" : s;
3258  };
3259  tree["empty_null"].set_serialized(tilde_if_predicate(null));
3260  tree["empty_nonnull"].set_serialized(tilde_if_predicate(nonnull));
3261  tree["str_null"].set_serialized(tilde_if_predicate(strnull));
3262  tree["str_tilde"].set_serialized(tilde_if_predicate(tilde));
3263  // this is the resulting yaml:
3264  CHECK(ryml::emitrs_yaml<std::string>(tree) == ""
3265  "empty_null: ~" "\n"
3266  "empty_nonnull: ''" "\n"
3267  "str_null: ~" "\n"
3268  "str_tilde: ~" "\n"
3269  "");
3270 }
bool scalar_is_null(csubstr s) noexcept
YAML-sense query of nullity.
C * str
a restricted pointer to the first character of the substring
Definition: substr.hpp:216
const char * str
Definition: node_type.cpp:70
csubstr arena() const
get the current arena
Definition: tree.hpp:1317
void clear_arena()
Definition: tree.hpp:340
void parse_in_arena(Parser *parser, csubstr filename, csubstr yaml, Tree *tree, id_type node_id)
(1) parse YAML into an existing tree node. The filename will be used in any error messages arising du...
Definition: parse.cpp:209
#define CHECK(predicate)
a testing assertion, used only in this quickstart
Definition: quickstart.cpp:310
Holds a pointer to an existing tree, and a node id.
Definition: node.hpp:736
void set_serialized(id_type node, T const &val) RYML_NOEXCEPT
Definition: tree.hpp:823
size_t len
the length of the substring
Definition: substr.hpp:218
NodeRef rootref()
Get the root as a NodeRef . Note that a non-const Tree implicitly converts to NodeRef.
Definition: tree.cpp:56
void clear()
clear the tree and zero every node
Definition: tree.cpp:330
void set_map()
Definition: node.hpp:1257
void sample_formatting ( )

control formatting when serializing/deserializing

ryml provides facilities for formatting/deformatting (imported from c4core into the ryml namespace).

See Format utilities . These functions are very useful to serialize and deserialize scalar types; see Serialization/deserialization .

Definition at line 3280 of file quickstart.cpp.

Referenced by main().

3281 {
3282  // format(), format_sub(), formatrs(): format arguments
3283  {
3284  char buf_[256] = {};
3285  ryml::substr buf = buf_;
3286  size_t size = ryml::format(buf, "a={} foo {} {} bar {}", 0.1, 10, 11, 12);
3287  CHECK(size == strlen("a=0.1 foo 10 11 bar 12"));
3288  CHECK(buf.first(size) == "a=0.1 foo 10 11 bar 12");
3289  // it is safe to call on an empty buffer:
3290  // returns the size needed for the result, and no overflow occurs:
3291  size = ryml::format({} , "a={} foo {} {} bar {}", "this_is_a", 10, 11, 12);
3292  CHECK(size == ryml::format(buf, "a={} foo {} {} bar {}", "this_is_a", 10, 11, 12));
3293  CHECK(size == strlen("a=this_is_a foo 10 11 bar 12"));
3294  // it is also safe to call on an insufficient buffer:
3295  char smallbuf[8] = {};
3296  size = ryml::format(smallbuf, "{} is too large {}", "this", "for the buffer");
3297  CHECK(size == strlen("this is too large for the buffer"));
3298  // ... and the result is truncated at the buffer size:
3299  CHECK(ryml::substr(smallbuf, sizeof(smallbuf)) == "this is\0");
3300 
3301  // format_sub() directly returns the written string:
3302  ryml::csubstr result = ryml::format_sub(buf, "b={}, damn it.", 1);
3303  CHECK(result == "b=1, damn it.");
3304  CHECK(result.is_sub(buf));
3305 
3306  // formatrs() means FORMAT & ReSize:
3307  //
3308  // Instead of a substr, it receives any owning linear char container
3309  // for which to_substr() is defined (using ADL).
3310  // <ryml_std.hpp> has to_substr() definitions for std::string and
3311  // std::vector<char>.
3312  //
3313  // formatrs() starts by calling format(), and if needed, resizes the container
3314  // and calls format() again.
3315  //
3316  // Note that unless the container is previously sized, this
3317  // may cause an allocation, which will make your code slower.
3318  // Make sure to call .reserve() on the container for real
3319  // production code.
3320  std::string sbuf;
3321  ryml::formatrs(&sbuf, "and c={} seems about right", 2);
3322  CHECK(sbuf == "and c=2 seems about right");
3323  std::vector<char> vbuf; // works with any linear char container
3324  ryml::formatrs(&vbuf, "and c={} seems about right", 2);
3325  CHECK(sbuf == "and c=2 seems about right");
3326  // with formatrs() it is also possible to append:
3327  ryml::formatrs_append(&sbuf, ", and finally d={} - done", 3);
3328  CHECK(sbuf == "and c=2 seems about right, and finally d=3 - done");
3329  }
3330 
3331  // unformat(): read arguments - opposite of format()
3332  {
3333  char buf_[256];
3334 
3335  int a = 0, b = 1, c = 2;
3336  ryml::csubstr result = ryml::format_sub(buf_, "{} and {} and {}", a, b, c);
3337  CHECK(result == "0 and 1 and 2");
3338  int aa = -1, bb = -2, cc = -3;
3339  size_t num_characters = ryml::unformat(result, "{} and {} and {}", aa, bb, cc);
3340  CHECK(num_characters != ryml::csubstr::npos); // if a conversion fails, returns ryml::csubstr::npos
3341  CHECK(num_characters == result.size());
3342  CHECK(aa == a);
3343  CHECK(bb == b);
3344  CHECK(cc == c);
3345 
3346  result = ryml::format_sub(buf_, "{} and {} and {}", 10, 20, 30);
3347  CHECK(result == "10 and 20 and 30");
3348  num_characters = ryml::unformat(result, "{} and {} and {}", aa, bb, cc);
3349  CHECK(num_characters != ryml::csubstr::npos); // if a conversion fails, returns ryml::csubstr::npos
3350  CHECK(num_characters == result.size());
3351  CHECK(aa == 10);
3352  CHECK(bb == 20);
3353  CHECK(cc == 30);
3354  }
3355 
3356  // cat(), cat_sub(), catrs(): concatenate arguments
3357  {
3358  char buf_[256] = {};
3359  ryml::substr buf = buf_;
3360  size_t size = ryml::cat(buf, "a=", 0.1, "foo", 10, 11, "bar", 12);
3361  CHECK(size == strlen("a=0.1foo1011bar12"));
3362  CHECK(buf.first(size) == "a=0.1foo1011bar12");
3363  // it is safe to call on an empty buffer:
3364  // returns the size needed for the result, and no overflow occurs:
3365  CHECK(ryml::cat({}, "a=", 0) == 3);
3366  // it is also safe to call on an insufficient buffer:
3367  char smallbuf[8] = {};
3368  size = ryml::cat(smallbuf, "this", " is too large ", "for the buffer");
3369  CHECK(size == strlen("this is too large for the buffer"));
3370  // ... and the result is truncated at the buffer size:
3371  CHECK(ryml::substr(smallbuf, sizeof(smallbuf)) == "this is\0");
3372 
3373  // cat_sub() directly returns the written string:
3374  ryml::csubstr result = ryml::cat_sub(buf, "b=", 1, ", damn it.");
3375  CHECK(result == "b=1, damn it.");
3376  CHECK(result.is_sub(buf));
3377 
3378  // catrs() means CAT & ReSize:
3379  //
3380  // Instead of a substr, it receives any owning linear char container
3381  // for which to_substr() is defined (using ADL).
3382  // <ryml_std.hpp> has to_substr() definitions for std::string and
3383  // std::vector<char>.
3384  //
3385  // catrs() starts by calling cat(), and if needed, resizes the container
3386  // and calls cat() again.
3387  //
3388  // Note that unless the container is previously sized, this
3389  // may cause an allocation, which will make your code slower.
3390  // Make sure to call .reserve() on the container for real
3391  // production code.
3392  std::string sbuf;
3393  ryml::catrs(&sbuf, "and c=", 2, " seems about right");
3394  CHECK(sbuf == "and c=2 seems about right");
3395  std::vector<char> vbuf; // works with any linear char container
3396  ryml::catrs(&vbuf, "and c=", 2, " seems about right");
3397  CHECK(sbuf == "and c=2 seems about right");
3398  // with catrs() it is also possible to append:
3399  ryml::catrs_append(&sbuf, ", and finally d=", 3, " - done");
3400  CHECK(sbuf == "and c=2 seems about right, and finally d=3 - done");
3401  }
3402 
3403  // uncat(): read arguments - opposite of cat()
3404  {
3405  char buf_[256];
3406 
3407  int a = 0, b = 1, c = 2;
3408  ryml::csubstr result = ryml::cat_sub(buf_, a, ' ', b, ' ', c);
3409  CHECK(result == "0 1 2");
3410  int aa = -1, bb = -2, cc = -3;
3411  char sep1 = 'a', sep2 = 'b';
3412  size_t num_characters = ryml::uncat(result, aa, sep1, bb, sep2, cc);
3413  CHECK(num_characters == result.size());
3414  CHECK(aa == a);
3415  CHECK(bb == b);
3416  CHECK(cc == c);
3417  CHECK(sep1 == ' ');
3418  CHECK(sep2 == ' ');
3419 
3420  result = ryml::cat_sub(buf_, 10, ' ', 20, ' ', 30);
3421  CHECK(result == "10 20 30");
3422  num_characters = ryml::uncat(result, aa, sep1, bb, sep2, cc);
3423  CHECK(num_characters == result.size());
3424  CHECK(aa == 10);
3425  CHECK(bb == 20);
3426  CHECK(cc == 30);
3427  CHECK(sep1 == ' ');
3428  CHECK(sep2 == ' ');
3429  }
3430 
3431  // catsep(), catsep_sub(), catseprs(): concatenate arguments, with a separator
3432  {
3433  char buf_[256] = {};
3434  ryml::substr buf = buf_;
3435  // use ' ' as a separator
3436  size_t size = ryml::catsep(buf, ' ', "a=", 0, "b=", 1, "c=", 2, 45, 67);
3437  CHECK(buf.first(size) == "a= 0 b= 1 c= 2 45 67");
3438  // any separator may be used
3439  // use " and " as a separator
3440  size = ryml::catsep(buf, " and ", "a=0", "b=1", "c=2", 45, 67);
3441  CHECK(buf.first(size) == "a=0 and b=1 and c=2 and 45 and 67");
3442  // use " ... " as a separator
3443  size = ryml::catsep(buf, " ... ", "a=0", "b=1", "c=2", 45, 67);
3444  CHECK(buf.first(size) == "a=0 ... b=1 ... c=2 ... 45 ... 67");
3445  // use '/' as a separator
3446  size = ryml::catsep(buf, '/', "a=", 0, "b=", 1, "c=", 2, 45, 67);
3447  CHECK(buf.first(size) == "a=/0/b=/1/c=/2/45/67");
3448  // use 888 as a separator
3449  size = ryml::catsep(buf, 888, "a=0", "b=1", "c=2", 45, 67);
3450  CHECK(buf.first(size) == "a=0888b=1888c=28884588867");
3451 
3452  // it is safe to call on an empty buffer:
3453  // returns the size needed for the result, and no overflow occurs:
3454  CHECK(size == ryml::catsep({}, 888, "a=0", "b=1", "c=2", 45, 67));
3455  // it is also safe to call on an insufficient buffer:
3456  char smallbuf[8] = {};
3457  CHECK(size == ryml::catsep(smallbuf, 888, "a=0", "b=1", "c=2", 45, 67));
3458  CHECK(size == strlen("a=0888b=1888c=28884588867"));
3459  // ... and the result is truncated:
3460  CHECK(ryml::substr(smallbuf, sizeof(smallbuf)) == "a=0888b\0");
3461 
3462  // catsep_sub() directly returns the written substr:
3463  ryml::csubstr result = ryml::catsep_sub(buf, " and ", "a=0", "b=1", "c=2", 45, 67);
3464  CHECK(result == "a=0 and b=1 and c=2 and 45 and 67");
3465  CHECK(result.is_sub(buf));
3466 
3467  // catseprs() means CATSEP & ReSize:
3468  //
3469  // Instead of a substr, it receives any owning linear char container
3470  // for which to_substr() is defined (using ADL).
3471  // <ryml_std.hpp> has to_substr() definitions for std::string and
3472  // std::vector<char>.
3473  //
3474  // catseprs() starts by calling catsep(), and if needed, resizes the container
3475  // and calls catsep() again.
3476  //
3477  // Note that unless the container is previously sized, this
3478  // may cause an allocation, which will make your code slower.
3479  // Make sure to call .reserve() on the container for real
3480  // production code.
3481  std::string sbuf;
3482  ryml::catseprs(&sbuf, " and ", "a=0", "b=1", "c=2", 45, 67);
3483  CHECK(sbuf == "a=0 and b=1 and c=2 and 45 and 67");
3484  std::vector<char> vbuf; // works with any linear char container
3485  ryml::catseprs(&vbuf, " and ", "a=0", "b=1", "c=2", 45, 67);
3486  CHECK(ryml::to_csubstr(vbuf) == "a=0 and b=1 and c=2 and 45 and 67");
3487 
3488  // with catseprs() it is also possible to append:
3489  ryml::catseprs_append(&sbuf, " well ", " --- a=0", "b=11", "c=12", 145, 167);
3490  CHECK(sbuf == "a=0 and b=1 and c=2 and 45 and 67 --- a=0 well b=11 well c=12 well 145 well 167");
3491  }
3492 
3493  // uncatsep(): read arguments with a separator - opposite of catsep()
3494  {
3495  char buf_[256] = {};
3496 
3497  int a = 0, b = 1, c = 2;
3498  ryml::csubstr result = ryml::catsep_sub(buf_, ' ', a, b, c);
3499  CHECK(result == "0 1 2");
3500  int aa = -1, bb = -2, cc = -3;
3501  size_t num_characters = ryml::uncatsep(result, " ", aa, bb, cc);
3502  CHECK(num_characters == result.size());
3503  CHECK(aa == a);
3504  CHECK(bb == b);
3505  CHECK(cc == c);
3506 
3507  result = ryml::catsep_sub(buf_, "--", 10, 20, 30);
3508  CHECK(result == "10--20--30");
3509  num_characters = ryml::uncatsep(result, "--", aa, bb, cc);
3510  CHECK(num_characters == result.size());
3511  CHECK(aa == 10);
3512  CHECK(bb == 20);
3513  CHECK(cc == 30);
3514  }
3515 
3516  // formatting individual arguments
3517  {
3518  using namespace ryml; // all the symbols below are in the ryml namespace.
3519  char buf_[256] = {}; // all the results below are written in this buffer
3520  substr buf = buf_;
3521  // --------------------------------------
3522  // fmt::boolalpha(): format as true/false
3523  // --------------------------------------
3524  // just as with std streams, printing a bool will output the integer value:
3525  CHECK("0" == cat_sub(buf, false));
3526  CHECK("1" == cat_sub(buf, true));
3527  // to force a "true"/"false", use fmt::boolalpha:
3528  CHECK("false" == cat_sub(buf, fmt::boolalpha(false)));
3529  CHECK("true" == cat_sub(buf, fmt::boolalpha(true)));
3530 
3531  // ---------------------------------
3532  // fmt::hex(): format as hexadecimal
3533  // ---------------------------------
3534  CHECK("0xff" == cat_sub(buf, fmt::hex(255)));
3535  CHECK("0x100" == cat_sub(buf, fmt::hex(256)));
3536  CHECK("-0xff" == cat_sub(buf, fmt::hex(-255)));
3537  CHECK("-0x100" == cat_sub(buf, fmt::hex(-256)));
3538  CHECK("3735928559" == cat_sub(buf, UINT32_C(0xdeadbeef)));
3539  CHECK("0xdeadbeef" == cat_sub(buf, fmt::hex(UINT32_C(0xdeadbeef))));
3540  // ----------------------------
3541  // fmt::bin(): format as binary
3542  // ----------------------------
3543  CHECK("0b1000" == cat_sub(buf, fmt::bin(8)));
3544  CHECK("0b1001" == cat_sub(buf, fmt::bin(9)));
3545  CHECK("0b10001" == cat_sub(buf, fmt::bin(17)));
3546  CHECK("0b11001" == cat_sub(buf, fmt::bin(25)));
3547  CHECK("-0b1000" == cat_sub(buf, fmt::bin(-8)));
3548  CHECK("-0b1001" == cat_sub(buf, fmt::bin(-9)));
3549  CHECK("-0b10001" == cat_sub(buf, fmt::bin(-17)));
3550  CHECK("-0b11001" == cat_sub(buf, fmt::bin(-25)));
3551  // ---------------------------
3552  // fmt::bin(): format as octal
3553  // ---------------------------
3554  CHECK("0o77" == cat_sub(buf, fmt::oct(63)));
3555  CHECK("0o100" == cat_sub(buf, fmt::oct(64)));
3556  CHECK("0o377" == cat_sub(buf, fmt::oct(255)));
3557  CHECK("0o400" == cat_sub(buf, fmt::oct(256)));
3558  CHECK("0o1000" == cat_sub(buf, fmt::oct(512)));
3559  CHECK("-0o77" == cat_sub(buf, fmt::oct(-63)));
3560  CHECK("-0o100" == cat_sub(buf, fmt::oct(-64)));
3561  CHECK("-0o377" == cat_sub(buf, fmt::oct(-255)));
3562  CHECK("-0o400" == cat_sub(buf, fmt::oct(-256)));
3563  CHECK("-0o1000" == cat_sub(buf, fmt::oct(-512)));
3564  // ---------------------------
3565  // fmt::zpad(): pad with zeros
3566  // ---------------------------
3567  CHECK("000063" == cat_sub(buf, fmt::zpad(63, 6)));
3568  CHECK( "00063" == cat_sub(buf, fmt::zpad(63, 5)));
3569  CHECK( "0063" == cat_sub(buf, fmt::zpad(63, 4)));
3570  CHECK( "063" == cat_sub(buf, fmt::zpad(63, 3)));
3571  CHECK( "63" == cat_sub(buf, fmt::zpad(63, 2)));
3572  CHECK( "63" == cat_sub(buf, fmt::zpad(63, 1))); // will never trim the result
3573  CHECK( "63" == cat_sub(buf, fmt::zpad(63, 0))); // will never trim the result
3574  CHECK("0x00003f" == cat_sub(buf, fmt::zpad(fmt::hex(63), 6)));
3575  CHECK("0o000077" == cat_sub(buf, fmt::zpad(fmt::oct(63), 6)));
3576  CHECK("0b00011001" == cat_sub(buf, fmt::zpad(fmt::bin(25), 8)));
3577  // ------------------------------------------------
3578  // fmt::left(): align left with a given field width
3579  // ------------------------------------------------
3580  CHECK("63 " == cat_sub(buf, fmt::left(63, 6)));
3581  CHECK("63 " == cat_sub(buf, fmt::left(63, 5)));
3582  CHECK("63 " == cat_sub(buf, fmt::left(63, 4)));
3583  CHECK("63 " == cat_sub(buf, fmt::left(63, 3)));
3584  CHECK("63" == cat_sub(buf, fmt::left(63, 2)));
3585  CHECK("63" == cat_sub(buf, fmt::left(63, 1))); // will never trim the result
3586  CHECK("63" == cat_sub(buf, fmt::left(63, 0))); // will never trim the result
3587  // the fill character can be specified (defaults to ' '):
3588  CHECK("63----" == cat_sub(buf, fmt::left(63, 6, '-')));
3589  CHECK("63++++" == cat_sub(buf, fmt::left(63, 6, '+')));
3590  CHECK("63////" == cat_sub(buf, fmt::left(63, 6, '/')));
3591  CHECK("630000" == cat_sub(buf, fmt::left(63, 6, '0')));
3592  CHECK("63@@@@" == cat_sub(buf, fmt::left(63, 6, '@')));
3593  CHECK("0x003f " == cat_sub(buf, fmt::left(fmt::zpad(fmt::hex(63), 4), 10)));
3594  // --------------------------------------------------
3595  // fmt::right(): align right with a given field width
3596  // --------------------------------------------------
3597  CHECK(" 63" == cat_sub(buf, fmt::right(63, 6)));
3598  CHECK(" 63" == cat_sub(buf, fmt::right(63, 5)));
3599  CHECK(" 63" == cat_sub(buf, fmt::right(63, 4)));
3600  CHECK(" 63" == cat_sub(buf, fmt::right(63, 3)));
3601  CHECK("63" == cat_sub(buf, fmt::right(63, 2)));
3602  CHECK("63" == cat_sub(buf, fmt::right(63, 1))); // will never trim the result
3603  CHECK("63" == cat_sub(buf, fmt::right(63, 0))); // will never trim the result
3604  // the fill character can be specified (defaults to ' '):
3605  CHECK("----63" == cat_sub(buf, fmt::right(63, 6, '-')));
3606  CHECK("++++63" == cat_sub(buf, fmt::right(63, 6, '+')));
3607  CHECK("////63" == cat_sub(buf, fmt::right(63, 6, '/')));
3608  CHECK("000063" == cat_sub(buf, fmt::right(63, 6, '0')));
3609  CHECK("@@@@63" == cat_sub(buf, fmt::right(63, 6, '@')));
3610  CHECK(" 0x003f" == cat_sub(buf, fmt::right(fmt::zpad(fmt::hex(63), 4), 10)));
3611 
3612  // ------------------------------------------
3613  // fmt::real(): format floating point numbers
3614  // ------------------------------------------
3615  // see also sample_float_precision()
3616  CHECK("0" == cat_sub(buf, fmt::real(0.01f, 0)));
3617  CHECK("0.0" == cat_sub(buf, fmt::real(0.01f, 1)));
3618  CHECK("0.01" == cat_sub(buf, fmt::real(0.01f, 2)));
3619  CHECK("0.010" == cat_sub(buf, fmt::real(0.01f, 3)));
3620  CHECK("0.0100" == cat_sub(buf, fmt::real(0.01f, 4)));
3621  CHECK("0.01000" == cat_sub(buf, fmt::real(0.01f, 5)));
3622  CHECK("1" == cat_sub(buf, fmt::real(1.01f, 0)));
3623  CHECK("1.0" == cat_sub(buf, fmt::real(1.01f, 1)));
3624  CHECK("1.01" == cat_sub(buf, fmt::real(1.01f, 2)));
3625  CHECK("1.010" == cat_sub(buf, fmt::real(1.01f, 3)));
3626  CHECK("1.0100" == cat_sub(buf, fmt::real(1.01f, 4)));
3627  CHECK("1.01000" == cat_sub(buf, fmt::real(1.01f, 5)));
3628  CHECK("1" == cat_sub(buf, fmt::real(1.234234234, 0)));
3629  CHECK("1.2" == cat_sub(buf, fmt::real(1.234234234, 1)));
3630  CHECK("1.23" == cat_sub(buf, fmt::real(1.234234234, 2)));
3631  CHECK("1.234" == cat_sub(buf, fmt::real(1.234234234, 3)));
3632  CHECK("1.2342" == cat_sub(buf, fmt::real(1.234234234, 4)));
3633  CHECK("1.23423" == cat_sub(buf, fmt::real(1.234234234, 5)));
3634  CHECK("1000000.00000" == cat_sub(buf, fmt::real(1000000.000000000, 5)));
3635  CHECK("1234234.23423" == cat_sub(buf, fmt::real(1234234.234234234, 5)));
3636  // AKA %f
3637  CHECK("1000000.00000" == cat_sub(buf, fmt::real(1000000.000000000, 5, FTOA_FLOAT))); // AKA %f, same as above
3638  CHECK("1234234.23423" == cat_sub(buf, fmt::real(1234234.234234234, 5, FTOA_FLOAT))); // AKA %f
3639  CHECK("1234234.2342" == cat_sub(buf, fmt::real(1234234.234234234, 4, FTOA_FLOAT))); // AKA %f
3640  CHECK("1234234.234" == cat_sub(buf, fmt::real(1234234.234234234, 3, FTOA_FLOAT))); // AKA %f
3641  CHECK("1234234.23" == cat_sub(buf, fmt::real(1234234.234234234, 2, FTOA_FLOAT))); // AKA %f
3642  // AKA %e
3643  CHECK("1.00000e+06" == cat_sub(buf, fmt::real(1000000.000000000, 5, FTOA_SCIENT))); // AKA %e
3644  CHECK("1.23423e+06" == cat_sub(buf, fmt::real(1234234.234234234, 5, FTOA_SCIENT))); // AKA %e
3645  CHECK("1.2342e+06" == cat_sub(buf, fmt::real(1234234.234234234, 4, FTOA_SCIENT))); // AKA %e
3646  CHECK("1.234e+06" == cat_sub(buf, fmt::real(1234234.234234234, 3, FTOA_SCIENT))); // AKA %e
3647  CHECK("1.23e+06" == cat_sub(buf, fmt::real(1234234.234234234, 2, FTOA_SCIENT))); // AKA %e
3648  // AKA %g
3649  CHECK("1e+06" == cat_sub(buf, fmt::real(1000000.000000000, 5, FTOA_FLEX))); // AKA %g
3650  CHECK("1.2342e+06" == cat_sub(buf, fmt::real(1234234.234234234, 5, FTOA_FLEX))); // AKA %g
3651  CHECK("1.234e+06" == cat_sub(buf, fmt::real(1234234.234234234, 4, FTOA_FLEX))); // AKA %g
3652  CHECK("1.23e+06" == cat_sub(buf, fmt::real(1234234.234234234, 3, FTOA_FLEX))); // AKA %g
3653  CHECK("1.2e+06" == cat_sub(buf, fmt::real(1234234.234234234, 2, FTOA_FLEX))); // AKA %g
3654  // FTOA_HEXA: AKA %a (hexadecimal formatting of floats)
3655  CHECK("0x1.e8480p+19" == cat_sub(buf, fmt::real(1000000.000000000, 5, FTOA_HEXA))); // AKA %a
3656  CHECK("0x1.2d53ap+20" == cat_sub(buf, fmt::real(1234234.234234234, 5, FTOA_HEXA))); // AKA %a
3657 
3658  // --------------------------------------------------------------
3659  // fmt::raw(): dump data in raw (binary) machine format (respecting alignment)
3660  // --------------------------------------------------------------
3661  {
3662  C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH("-Wcast-align") // we're casting the values directly, so alignment is strictly respected.
3663  const uint32_t payload[] = {10, 20, 30, 40, UINT32_C(0xdeadbeef)};
3664  // (package payload as a substr, for comparison only)
3665  csubstr expected = csubstr((const char *)payload, sizeof(payload));
3666  csubstr actual = cat_sub(buf, fmt::raw(payload));
3667  CHECK(!actual.overlaps(expected));
3668  CHECK(0 == memcmp(expected.str, actual.str, expected.len));
3669  // also possible with variables:
3670  for(const uint32_t value : payload)
3671  {
3672  // (package payload as a substr, for comparison only)
3673  expected = csubstr((const char *)&value, sizeof(value));
3674  actual = cat_sub(buf, fmt::raw(value));
3675  CHECK(actual.size() == sizeof(uint32_t));
3676  CHECK(!actual.overlaps(expected));
3677  CHECK(0 == memcmp(expected.str, actual.str, expected.len));
3678  // with non-const data, fmt::craw() may be needed for disambiguation:
3679  actual = cat_sub(buf, fmt::craw(value));
3680  CHECK(actual.size() == sizeof(uint32_t));
3681  CHECK(!actual.overlaps(expected));
3682  CHECK(0 == memcmp(expected.str, actual.str, expected.len));
3683  //
3684  // read back:
3685  uint32_t result = 0;
3686  auto reader = fmt::raw(result);
3687  CHECK(uncat(actual, reader));
3688  // and compare:
3689  // (vs2017/release/32bit does not reload result from cache, so force it)
3690  CHECK(result == value); // roundtrip completed successfully
3691  }
3692  C4_SUPPRESS_WARNING_GCC_CLANG_POP
3693  }
3694 
3695  // -------------------------
3696  // fmt::base64(): see below!
3697  // -------------------------
3698  }
3699 }
print the real number in hexadecimal format (like a)
Definition: charconv.hpp:200
csubstr formatrs_append(CharOwningContainer *cont, csubstr fmt, Args const &...args)
format+resize+append: like c4::format(), but receives a container, and appends the arguments...
Definition: format.hpp:1330
bool overlaps(ro_substr const that) const noexcept
true if there is overlap of at least one element between that and *this
Definition: substr.hpp:493
void catseprs(CharOwningContainer *cont, Sep const &sep, Args const &...args)
catsep+resize: like c4::catsep(), but receives a container, and resizes it as needed to contain the r...
Definition: format.hpp:1152
size_t uncatsep(csubstr buf, csubstr sep, Arg &a, Args &...more)
deserialize the arguments from the given buffer, using a separator.
Definition: format.hpp:819
print the real number in flexible format (like g)
Definition: charconv.hpp:198
print the real number in scientific format (like e)
Definition: charconv.hpp:196
size_t unformat(csubstr buf, csubstr fmt, Arg &a, Args &...more)
using a format string, deserialize the arguments from the given buffer.
Definition: format.hpp:987
substr format_sub(substr buf, csubstr fmt, Args const &...args)
like c4::format() but return a substr instead of a size
Definition: format.hpp:956
C * str
a restricted pointer to the first character of the substring
Definition: substr.hpp:216
size_t format(substr buf, csubstr fmt, Arg const &a, Args const &...more)
Using a format string, serialize the arguments into the given fixed-size buffer.
Definition: format.hpp:936
integral_< intptr_t > hex(std::nullptr_t)
format null as an hexadecimal value
Definition: format.hpp:160
void catrs(CharOwningContainer *cont, Args const &...args)
cat+resize: like c4::cat(), but receives a container, and resizes it as needed to contain the result...
Definition: format.hpp:1040
const_raw_wrapper craw(cblob data, size_t alignment=alignof(max_align_t))
mark a variable to be written in raw binary format, using memcpy
Definition: format.hpp:412
size_t cat(substr buf, Arg const &a, Args const &...more)
serialize the arguments, concatenating them to the given fixed-size buffer.
Definition: format.hpp:649
print the real number in floating point format (like f)
Definition: charconv.hpp:194
basic_substring first(size_t num) const noexcept
return the first num elements: [0,num[
Definition: substr.hpp:529
real_< T > real(T val, int precision, RealFormat_e fmt=FTOA_FLOAT)
Definition: format.hpp:362
integral_< intptr_t > bin(std::nullptr_t)
format null as a binary 0-1 value
Definition: format.hpp:212
size_t size() const noexcept
Definition: substr.hpp:358
integral_< intptr_t > oct(std::nullptr_t)
format null as an octal value
Definition: format.hpp:185
csubstr to_csubstr(const char(&s)[N]) noexcept
Definition: substr.hpp:2380
basic_substring< char > substr
a mutable string view
Definition: substr.hpp:2355
basic_substring< const char > csubstr
an immutable string view
Definition: substr.hpp:2356
substr cat_sub(substr buf, Args const &...args)
like c4::cat() but return a substr instead of a size
Definition: format.hpp:659
csubstr catrs_append(CharOwningContainer *cont, Args const &...args)
cat+resize+append: like c4::cat(), but receives a container, and appends to it instead of overwriting...
Definition: format.hpp:1109
#define CHECK(predicate)
a testing assertion, used only in this quickstart
Definition: quickstart.cpp:310
Definition: ryml.hpp:6
size_t len
the length of the substring
Definition: substr.hpp:218
csubstr catseprs_append(CharOwningContainer *cont, Sep const &sep, Args const &...args)
catsep+resize+append: like c4::catsep(), but receives a container, and appends the arguments...
Definition: format.hpp:1220
size_t catsep(substr buf, Sep const &sep, Arg const &a, Args const &...more)
serialize the arguments, concatenating them to the given fixed-size buffer, using a separator between...
Definition: format.hpp:774
void formatrs(CharOwningContainer *cont, csubstr fmt, Args const &...args)
format+resize: like c4::format(), but receives a container, and resizes it as needed to contain the r...
Definition: format.hpp:1263
boolalpha_ boolalpha(T const &val=false)
tag function to mark a variable to be written as an alphabetic boolean, ie as either true or false ...
Definition: format.hpp:70
substr catsep_sub(substr buf, Args &&...args)
like c4::catsep() but return a substr instead of a size
Definition: format.hpp:786
integral_padded_< T > zpad(T val, size_t num_digits)
pad the argument with zeroes on the left, with decimal radix
Definition: format.hpp:232
left_< T > left(T val, size_t width, char padchar=' ')
tag type to mark an argument to be aligned left.
Definition: format.hpp:515
const_raw_wrapper raw(cblob data, size_t alignment=alignof(max_align_t))
mark a variable to be written in raw binary format, using memcpy
Definition: format.hpp:418
right_< T > right(T val, size_t width, char padchar=' ')
tag function to mark an argument to be aligned right
Definition: format.hpp:553
bool is_sub(ro_substr const that) const noexcept
true if *this is a substring of that (ie, from the same buffer)
Definition: substr.hpp:478
size_t uncat(csubstr buf, Arg &a, Args &...more)
deserialize the arguments from the given buffer.
Definition: format.hpp:690
void sample_base64 ( )

encode/decode base64

demonstrates how to read and write base64-encoded blobs.

See also
Base64 encoding/decoding

Definition at line 3706 of file quickstart.cpp.

Referenced by main().

3707 {
3708  // let's start by creating a tree with base64 vals and keys
3709  ryml::Tree tree;
3710  tree.rootref().set_map();
3711  struct text_and_base64 { ryml::csubstr text, base64; };
3712  text_and_base64 cases[] = {
3713  {{"Hello, World!"}, {"SGVsbG8sIFdvcmxkIQ=="}},
3714  {{"Brevity is the soul of wit."}, {"QnJldml0eSBpcyB0aGUgc291bCBvZiB3aXQu"}},
3715  {{"All that glitters is not gold."}, {"QWxsIHRoYXQgZ2xpdHRlcnMgaXMgbm90IGdvbGQu"}},
3716  };
3717  // to encode base64 and write the result to val:
3718  for(text_and_base64 c : cases)
3719  tree[c.text].set_serialized(ryml::fmt::base64(c.text));
3720  // to encode base64 and write the result to key:
3721  for(text_and_base64 c : cases)
3722  {
3723  ryml::NodeRef ch = tree.rootref().append_child();
3725  ch.set_serialized(c.text);
3726  }
3727  // check the result:
3728  for(text_and_base64 c : cases)
3729  {
3730  CHECK(tree[c.text].val() == c.base64);
3731  CHECK(tree[c.base64].val() == c.text);
3732  }
3733  // and this is how the YAML now looks:
3734  CHECK(ryml::emitrs_yaml<std::string>(tree) == ""
3735  "Hello, World!: SGVsbG8sIFdvcmxkIQ==" "\n"
3736  "Brevity is the soul of wit.: QnJldml0eSBpcyB0aGUgc291bCBvZiB3aXQu" "\n"
3737  "All that glitters is not gold.: QWxsIHRoYXQgZ2xpdHRlcnMgaXMgbm90IGdvbGQu" "\n"
3738  // note that the keys below are base64-encoded
3739  "SGVsbG8sIFdvcmxkIQ==: Hello, World!" "\n"
3740  "QnJldml0eSBpcyB0aGUgc291bCBvZiB3aXQu: Brevity is the soul of wit." "\n"
3741  "QWxsIHRoYXQgZ2xpdHRlcnMgaXMgbm90IGdvbGQu: All that glitters is not gold." "\n"
3742  "");
3743  char buf1_[128], buf2_[128];
3744  ryml::substr buf1 = buf1_; // this is where we will write the result (using load())
3745  ryml::substr buf2 = buf2_; // this is where we will write the result (using deserialize()/deserialize_key())
3746  // to decode base64 and write the result to buf:
3747  for(const text_and_base64 c : cases)
3748  {
3749  // this decodes base64 and write the decoded result into an
3750  // existing buffer (buf1):
3751  size_t len = 0; // the decoded length
3752  tree[c.text].load(ryml::fmt::base64(buf1, &len));
3753  // The base64() tag function is used to get the
3754  // deserialization using c4::decode_base64(). This will
3755  // respect the limits of the buffer, and fail with an error if
3756  // the buffer is too small (or if the base64 encoding is
3757  // wrong). The optional second parameter is set to the decoded
3758  // size, ie, the length of the decoded result, which is also
3759  // the size required for the buffer.
3760  CHECK(len <= buf1.len);
3761  CHECK(c.text.len == len);
3762  CHECK(buf1.first(len) == c.text);
3763  // likewise for keys:
3764  tree[c.base64].load_key(ryml::fmt::base64(buf2, &len));
3765  CHECK(len <= buf2.len);
3766  CHECK(buf2.first(len) == c.text);
3767  //
3768  // interop with std::string:
3769  std::string result;
3770  tree[c.text].load(ryml::fmt::base64(result));
3771  CHECK(result == c.text);
3772  // likewise for keys:
3773  tree[c.base64].load_key(ryml::fmt::base64(result));
3774  CHECK(result == c.text);
3775  //
3776  // Manual interop with std::string: using substr.
3777  // This shows how to manually resize the destination
3778  // buffer, and is similar to the implementation for containers.
3779  result.clear(); // this is not needed. We do it just to show that the first call can fail.
3780  len = 0;
3781  // try to read into the buffer, and get back the required size
3782  // (in len)
3783  auto payload = ryml::fmt::base64(ryml::to_substr(result), &len);
3784  bool ok = tree[c.text].deserialize(payload);
3785  if(len > result.size()) // the size was not enough; resize and call again
3786  {
3787  CHECK(!ok);
3788  result.resize(len);
3789  payload = ryml::fmt::base64(ryml::to_substr(result), &len); // reassign
3790  ok = tree[c.text].deserialize(payload);
3791  }
3792  CHECK(ok);
3793  result.resize(len); // trim to the length of the decoded buffer
3794  CHECK(result == c.text);
3795  // likewise for keys
3796  }
3797  //
3798  // ryml base64() serialization uses native endianness. If you're
3799  // encoding types whose size > 1 byte, the results will vary
3800  // according to endianess. Let's use a helper here to work around
3801  // that (in practice, you should use something like htons() before
3802  // encoding):
3803  union { uint32_t u; char c[sizeof(uint32_t)]; } endianess_test = {1};
3804  const bool is_little_endian = endianess_test.c[0] == 1; // NOLINT
3805  auto endian_select = [is_little_endian](ryml::csubstr little_endian, ryml::csubstr big_endian){
3806  return is_little_endian ? little_endian : big_endian;
3807  };
3808  //
3809  // directly encode variables: integers
3810  {
3811  const uint64_t valin = UINT64_C(0xdeadbeef);
3812  ryml::NodeRef node = tree["deadbeef"];
3814  CHECK(node.val() == endian_select("776t3gAAAAA=", "AAAAAN6tvu8="));
3815  uint64_t valout = 0;
3816  size_t len = 0;
3817  node.load(ryml::fmt::base64(valout, &len));
3818  CHECK(len == sizeof(valout));
3819  CHECK(valout == UINT64_C(0xdeadbeef)); // base64 roundtrip is bit-accurate
3820  // also works without length parameter:
3821  valout = {};
3822  node.load(ryml::fmt::base64(valout));
3823  CHECK(valout == UINT64_C(0xdeadbeef)); // base64 roundtrip is bit-accurate
3824  }
3825  // directly encode variables: floating point
3826  {
3827  const double valin = 123456.7891011;
3828  ryml::NodeRef node = tree["float"];
3830  CHECK(node.val() == endian_select("nHkooAwk/kA=", "QP4kDKAoeZw="));
3831  double valout = 0;
3832  size_t len = 0;
3833  node.load(ryml::fmt::base64(valout, &len));
3834  CHECK(len == sizeof(valout));
3835  CHECK(memcmp(&valout, &valin, sizeof(valout)) == 0); // base64 roundtrip is bit-accurate // NOLINT
3836  // also works without length parameter:
3837  valout = {};
3838  node.load(ryml::fmt::base64(valout));
3839  CHECK(memcmp(&valout, &valin, sizeof(valout)) == 0); // base64 roundtrip is bit-accurate // NOLINT
3840  }
3841  // directly encode memory ranges
3842  {
3843  const uint32_t data_in[11] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xdeadbeef};
3844  uint32_t data_out[11] = {};
3845  ryml::NodeRef node = tree["int_data"];
3846  node.set_serialized(ryml::fmt::base64(data_in, C4_COUNTOF(data_in)), ryml::VAL_PLAIN);
3847  CHECK(node.val() ==
3848  endian_select("AAAAAAEAAAACAAAAAwAAAAQAAAAFAAAABgAAAAcAAAAIAAAACQAAAO++rd4=",
3849  "AAAAAAAAAAEAAAACAAAAAwAAAAQAAAAFAAAABgAAAAcAAAAIAAAACd6tvu8="));
3850  CHECK(memcmp(data_in, data_out, sizeof(data_in)) != 0); // before the roundtrip
3851  size_t len = 0;
3852  node.load(ryml::fmt::base64(data_out, C4_COUNTOF(data_in), &len));
3853  CHECK(len == sizeof(data_out));
3854  CHECK(memcmp(data_in, data_out, sizeof(data_in)) == 0); // after the roundtrip
3855  // also works without length parameter (because data_out is an
3856  // array and not a pointer):
3857  memset(data_out, 0, sizeof(data_out));
3858  node.load(ryml::fmt::base64(data_out));
3859  CHECK(memcmp(data_in, data_out, sizeof(data_in)) == 0); // after the roundtrip
3860  }
3861 }
csubstr const & val(id_type node) const
Definition: tree.hpp:460
mark val scalar as plain scalar (unquoted, even when multiline)
Definition: node_type.hpp:156
void load_key(id_type node, T *k, bool check_readable=true) const
(1) deserialize the node's key (necessarily a scalar) to the given variable, forwarding to the user-o...
Definition: tree.hpp:912
void load(id_type node, T *v, bool check_readable=true) const
(1) deserialize the node's contents (val or container) to the given variable, forwarding to the user-...
Definition: tree.hpp:871
substr to_substr(char(&s)[N]) noexcept
Definition: substr.hpp:2376
void load(T *v, bool check_readable=true) const
(1) deserialize the node's contents (val or container) to the given variable, forwarding to the user-...
Definition: node.hpp:345
basic_substring first(size_t num) const noexcept
return the first num elements: [0,num[
Definition: substr.hpp:529
A reference to a node in an existing yaml tree, offering a more convenient API than the index-based A...
Definition: node.hpp:1062
ReadResult deserialize(id_type node, T *v) const
(1) deserialize a node's contents to a variable
Definition: tree.hpp:954
#define CHECK(predicate)
a testing assertion, used only in this quickstart
Definition: quickstart.cpp:310
void set_serialized(id_type node, T const &val) RYML_NOEXCEPT
Definition: tree.hpp:823
size_t len
the length of the substring
Definition: substr.hpp:218
NodeRef rootref()
Get the root as a NodeRef . Note that a non-const Tree implicitly converts to NodeRef.
Definition: tree.cpp:56
csubstr val() const RYML_NOEXCEPT
Forward to Tree::val().
Definition: node.hpp:179
const_base64_wrapper base64(csubstr s, size_t *reqsize=nullptr)
a tag function to mark a csubstr payload to be encoded in base64 format
NodeRef append_child()
Definition: node.hpp:1715
void set_serialized(T const &v)
serialize a variable to this node.
Definition: node.hpp:1375
void set_key_serialized(T const &k)
serialize a variable, then assign the result to the node's key
Definition: node.hpp:1396
void set_map()
Definition: node.hpp:1257
void sample_serialize_basic ( )

serialize/deserialize fundamental types

This sample shows the main user-facing calls triggering (de)serialization.

ryml provides built-ins for all fundamental types. For samples on how to implement other types such as STL containers or user types, see the samples below.

Read also the doxygen intro to using serialization

And likewise, you can use these from the tree as well: c4::yml::Tree::set_serialized() / c4::yml::Tree::set_key_serialized().

For deserialization, use from_chars().

Definition at line 3874 of file quickstart.cpp.

Referenced by main().

3875 {
3876  ryml::csubstr yaml = "{0: 0, 10: 10, foo: foo}";
3877  ryml::Tree tree = ryml::parse_in_arena(yaml);
3878  //
3879  //
3880  // Deserialization is done with .load() and .load_key()
3881  { int val = 1; tree[0].load(&val); CHECK(val == 0); }
3882  { unsigned val = 0; tree[1].load(&val); CHECK(val == 10); }
3883  { int key = 1; tree[0].load_key(&key); CHECK(key == 0); }
3884  { unsigned key = 0; tree[1].load_key(&key); CHECK(key == 10); }
3885  // also available in the tree:
3886  { int val = 1; tree.load(tree[0].id(), &val); CHECK(val == 0); }
3887  { unsigned val = 0; tree.load(tree[1].id(), &val); CHECK(val == 10); }
3888  { int key = 1; tree.load_key(tree[0].id(), &key); CHECK(key == 0); }
3889  { unsigned key = 0; tree.load_key(tree[1].id(), &key); CHECK(key == 10); }
3890  // .load() calls the (non returning) error callback when the
3891  // serialization fails. If you want to avoid the exceptional flow,
3892  // you can use .deserialize() / .deserialize_key(), and do not forget to
3893  // check its return status (marked as `[[nodiscard]]`):
3894  { int val = 1; CHECK(tree[0].deserialize(&val)); CHECK(val == 0); }
3895  { unsigned val = 0; CHECK(tree[1].deserialize(&val)); CHECK(val == 10); }
3896  { int key = 1; CHECK(tree[0].deserialize_key(&key)); CHECK(key == 0); }
3897  { unsigned key = 0; CHECK(tree[1].deserialize_key(&key)); CHECK(key == 10); }
3898  // also available in the tree:
3899  { int val = 1; CHECK(tree.deserialize(tree[0].id(), &val)); CHECK(val == 0); }
3900  { unsigned val = 0; CHECK(tree.deserialize(tree[1].id(), &val)); CHECK(val == 10); }
3901  { int key = 1; CHECK(tree.deserialize_key(tree[0].id(), &key)); CHECK(key == 0); }
3902  { unsigned key = 0; CHECK(tree.deserialize_key(tree[1].id(), &key)); CHECK(key == 10); }
3903  //
3904  //
3905  // Serialization is done with .save() and .save_key(). It is
3906  // carried out by converting the value to string in the tree's
3907  // arena (see sample_tree_arena()).
3908  { int val = 10; tree[0].save(val); CHECK(tree[0].val() == "10"); }
3909  { unsigned val = 11; tree[1].save(val); CHECK(tree[1].val() == "11"); }
3910  { int key = 12; tree[0].save_key(key); CHECK(tree[0].key() == "12"); }
3911  { unsigned key = 13; tree[1].save_key(key); CHECK(tree[1].key() == "13"); }
3912  // also available in the tree:
3913  { int val = 20; tree.save(tree[0].id(), val); CHECK(tree[0].val() == "20"); }
3914  { unsigned val = 21; tree.save(tree[1].id(), val); CHECK(tree[1].val() == "21"); }
3915  { int key = 22; tree.save_key(tree[0].id(), key); CHECK(tree[0].key() == "22"); }
3916  { unsigned key = 23; tree.save_key(tree[1].id(), key); CHECK(tree[1].key() == "23"); }
3917  //
3918  //
3919  // Like .load(), .save() checks the node for write-ability and
3920  // triggers an error if it failed. Likewise, to avoid the
3921  // exceptional path, you can use .set_serialized() and
3922  // .set_key_serialized():
3923  { int val = 14; tree[0].set_serialized(val); CHECK(tree[0].val() == "14"); }
3924  { unsigned val = 15; tree[1].set_serialized(val); CHECK(tree[1].val() == "15"); }
3925  { int key = 16; tree[0].set_key_serialized(key); CHECK(tree[0].key() == "16"); }
3926  { unsigned key = 17; tree[1].set_key_serialized(key); CHECK(tree[1].key() == "17"); }
3927  /// And likewise, you can use these from the tree as well:
3928  /// @ref c4::yml::Tree::set_serialized() / @ref c4::yml::Tree::set_key_serialized().
3929  { int val = 18; tree.set_serialized(tree[0].id(), val); CHECK(tree[0].val() == "18"); }
3930  { unsigned val = 19; tree.set_serialized(tree[1].id(), val); CHECK(tree[1].val() == "19"); }
3931  { int key = 20; tree.set_key_serialized(tree[0].id(), key); CHECK(tree[0].key() == "20"); }
3932  { unsigned key = 21; tree.set_key_serialized(tree[1].id(), key); CHECK(tree[1].key() == "21"); }
3933  //
3934  //
3935  // You can also (de)serialize tags and even anchors, if your
3936  // application requires it. For serialization the trick is to use
3937  // .to_arena():
3938  tree[0].set_val_tag(tree[0].to_arena(42)); CHECK(tree[0].val_tag() == "42");
3939  tree[1].set_key_tag(tree[1].to_arena(43)); CHECK(tree[1].key_tag() == "43");
3940  tree[0].set_val_anchor(tree[0].to_arena(44)); CHECK(tree[0].val_anchor() == "44");
3941  tree[1].set_key_anchor(tree[1].to_arena(45)); CHECK(tree[1].key_anchor() == "45");
3942  /// For deserialization, use from_chars().
3943  { int val = 0; CHECK(from_chars(tree[0].val_tag(), &val)); CHECK(val == 42); }
3944  { unsigned key = 0; CHECK(from_chars(tree[1].key_tag(), &key)); CHECK(key == 43); }
3945  { int val = 0; CHECK(from_chars(tree[0].val_anchor(), &val)); CHECK(val == 44); }
3946  { unsigned key = 0; CHECK(from_chars(tree[1].key_anchor(), &key)); CHECK(key == 45); }
3947 }
void load_key(id_type node, T *k, bool check_readable=true) const
(1) deserialize the node's key (necessarily a scalar) to the given variable, forwarding to the user-o...
Definition: tree.hpp:912
void load(id_type node, T *v, bool check_readable=true) const
(1) deserialize the node's contents (val or container) to the given variable, forwarding to the user-...
Definition: tree.hpp:871
void set_key_serialized(id_type node, T const &key) RYML_NOEXCEPT
Definition: tree.hpp:837
void set_key_tag(id_type node, csubstr tag)
Definition: tree.hpp:742
void parse_in_arena(Parser *parser, csubstr filename, csubstr yaml, Tree *tree, id_type node_id)
(1) parse YAML into an existing tree node. The filename will be used in any error messages arising du...
Definition: parse.cpp:209
bool from_chars(ryml::csubstr buf, vec2< T > *v)
void set_val_anchor(id_type node, csubstr anchor)
Definition: tree.hpp:746
ReadResult deserialize(id_type node, T *v) const
(1) deserialize a node's contents to a variable
Definition: tree.hpp:954
#define CHECK(predicate)
a testing assertion, used only in this quickstart
Definition: quickstart.cpp:310
void set_serialized(id_type node, T const &val) RYML_NOEXCEPT
Definition: tree.hpp:823
void set_key_anchor(id_type node, csubstr anchor)
Definition: tree.hpp:745
ReadResult deserialize_key(id_type node, T *v) const
(1) deserialize a node's key to a variable
Definition: tree.hpp:975
id_type id(NodeData const *n) const
get the id of a node belonging to this tree. n can be nullptr, in which case NONE is returned n must ...
Definition: tree.hpp:392
void set_val_tag(id_type node, csubstr tag)
Definition: tree.hpp:743
void save_key(id_type node, T const &key)
Definition: tree.hpp:794
void save(id_type node, T const &val)
Definition: tree.hpp:772
void sample_user_scalar_types ( )

serialize/deserialize scalar (leaf/scalar) types

to add scalar types (ie leaf types converting to/from string), define the functions above for those types.

Note
read the doxygen documentation for scalar types at: https://rapidyaml.readthedocs.io/v0.16.0/doxygen/group__doc__serialization__user__types.html

See Serialize/deserialize scalar types.

Definition at line 4018 of file quickstart.cpp.

Referenced by main().

4019 {
4020  ryml::Tree t;
4021  ryml::NodeRef root = t;
4022  root.set_map();
4023 
4024  vec2<int> v2in{10, 11};
4025  vec2<int> v2out{1, 2};
4026  root["v2"].save(v2in); // serializes to the tree's arena, and then sets the val
4027  root["v2"].load(&v2out);
4028  CHECK(v2in.x == v2out.x);
4029  CHECK(v2in.y == v2out.y);
4030  vec3<int> v3in{100, 101, 102};
4031  vec3<int> v3out{1, 2, 3};
4032  root["v3"].save(v3in); // serializes to the tree's arena, and then sets the val
4033  root["v3"].load(&v3out);
4034  CHECK(v3in.x == v3out.x);
4035  CHECK(v3in.y == v3out.y);
4036  CHECK(v3in.z == v3out.z);
4037  vec4<int> v4in{1000, 1001, 1002, 1003};
4038  vec4<int> v4out{1, 2, 3, 4};
4039  root["v4"].save(v4in); // serializes to the tree's arena, and then sets the val
4040  root["v4"].load(&v4out);
4041  CHECK(v4in.x == v4out.x);
4042  CHECK(v4in.y == v4out.y);
4043  CHECK(v4in.z == v4out.z);
4044  CHECK(v4in.w == v4out.w);
4045  CHECK(ryml::emitrs_yaml<std::string>(t) == ""
4046  "v2: (10,11)" "\n"
4047  "v3: (100,101,102)" "\n"
4048  "v4: (1000,1001,1002,1003)" "\n"
4049  "");
4050 
4051  // note that only the used functions are needed:
4052  // - if a type is only parsed, then only from_chars() is needed
4053  // - if a type is only emitted, then only to_chars() is needed
4054  emit_only_vec2<int> eov2in{20, 21}; // only has to_chars()
4055  parse_only_vec2<int> pov2out{1, 2}; // only has from_chars()
4056  root["v2"].save(eov2in); // serializes to the tree's arena, and then sets the keyval
4057  root["v2"].load(&pov2out);
4058  CHECK(eov2in.x == pov2out.x);
4059  CHECK(eov2in.y == pov2out.y);
4060  emit_only_vec3<int> eov3in{30, 31, 32}; // only has to_chars()
4061  parse_only_vec3<int> pov3out{1, 2, 3}; // only has from_chars()
4062  root["v3"].save(eov3in); // serializes to the tree's arena, and then sets the keyval
4063  root["v3"].load(&pov3out);
4064  CHECK(eov3in.x == pov3out.x);
4065  CHECK(eov3in.y == pov3out.y);
4066  CHECK(eov3in.z == pov3out.z);
4067  emit_only_vec4<int> eov4in{40, 41, 42, 43}; // only has to_chars()
4068  parse_only_vec4<int> pov4out{1, 2, 3, 4}; // only has from_chars()
4069  root["v4"].save(eov4in); // serializes to the tree's arena, and then sets the keyval
4070  root["v4"].load(&pov4out);
4071  CHECK(eov4in.x == pov4out.x);
4072  CHECK(eov4in.y == pov4out.y);
4073  CHECK(eov4in.z == pov4out.z);
4074  CHECK(ryml::emitrs_yaml<std::string>(t) == ""
4075  "v2: (20,21)" "\n"
4076  "v3: (30,31,32)" "\n"
4077  "v4: (40,41,42,43)" "\n"
4078  "");
4079 }
example scalar type, serialized only
void load(T *v, bool check_readable=true) const
(1) deserialize the node's contents (val or container) to the given variable, forwarding to the user-...
Definition: node.hpp:345
example scalar type, deserialized only
example scalar type, serialized only
A reference to a node in an existing yaml tree, offering a more convenient API than the index-based A...
Definition: node.hpp:1062
void save(T const &k)
Definition: node.hpp:1337
#define CHECK(predicate)
a testing assertion, used only in this quickstart
Definition: quickstart.cpp:310
example scalar type, deserialized only
example scalar type, serialized only
example scalar type, deserialized only
void set_map()
Definition: node.hpp:1257
void sample_user_container_types_brief ( )

serialize/deserialize container (map or seq) types: brief version

shows a minimal example of user container types, defined in Serialize container types (brief)

Note
read the doxygen documentation for containers/general types at: https://rapidyaml.readthedocs.io/v0.16.0/doxygen/group__doc__serialization__user__types.html

Definition at line 4143 of file quickstart.cpp.

Referenced by main().

4144 {
4145  ryml::Tree tree;
4146  ryml::NodeRef root = tree;
4147  Outer outer{{{0,1}, {2,3}}};
4148  Outer roundtrip;
4149  // let's do a serialization roundtrip.
4150  // first serialize:
4151  root.save(outer);
4152  // see how the YAML looks:
4153  CHECK(ryml::emitrs_yaml<std::string>(tree) == ""
4154  "- {foo: 0,bar: 1}\n"
4155  "- {foo: 2,bar: 3}\n");
4156  // now complete the roundtrip by deserializing:
4157  root.load(&roundtrip);
4158  // finally, verify that the result is equal:
4159  CHECK(0 == memcmp(&outer, &roundtrip, sizeof(Outer)));
4160 }
void load(T *v, bool check_readable=true) const
(1) deserialize the node's contents (val or container) to the given variable, forwarding to the user-...
Definition: node.hpp:345
serializes as a map
A reference to a node in an existing yaml tree, offering a more convenient API than the index-based A...
Definition: node.hpp:1062
void save(T const &k)
Definition: node.hpp:1337
#define CHECK(predicate)
a testing assertion, used only in this quickstart
Definition: quickstart.cpp:310
void sample_user_container_types ( )

serialize/deserialize container (map or seq) types

shows how to serialize/deserialize container types (defined in Serialize container types).

Note
read the doxygen documentation for containers/general types at: https://rapidyaml.readthedocs.io/v0.16.0/doxygen/group__doc__serialization__user__types.html
See also
Serialize container types
sample_std_types
sample_deserialize_error

Definition at line 4414 of file quickstart.cpp.

Referenced by main().

4415 {
4416  // let's do a YAML roundtrip:
4417  ryml::Tree tree;
4418  ryml::NodeRef root_node = tree;
4419  ryml::id_type root_id = tree.root_id();
4420 
4421  // here we will be doing a serialization roundtrip with a
4422  // user-defined container type.
4423  //
4424  // the read() and write() functions for this type are defined
4425  // above.
4426  const my_type orig{
4427  {20, 21},
4428  {30, 31, 32},
4429  {40, 41, 42, 43},
4430  {{101, 102, 103, 104, 105, 106, 107}},
4431  {{{1001, 2001}, {1002, 2002}, {1003, 2003}}},
4432  };
4433 
4434  // serialize to the tree:
4435  root_node.save(orig);
4436  // check the YAML:
4437  CHECK(ryml::emitrs_yaml<std::string>(tree) == ""
4438  "v2: (20,21)" "\n"
4439  "v3: (30,31,32)" "\n"
4440  "v4: (40,41,42,43)" "\n"
4441  "seq:" "\n"
4442  " - 101" "\n"
4443  " - 102" "\n"
4444  " - 103" "\n"
4445  " - 104" "\n"
4446  " - 105" "\n"
4447  " - 106" "\n"
4448  " - 107" "\n"
4449  "map:" "\n"
4450  " 1001: 2001" "\n"
4451  " 1002: 2002" "\n"
4452  " 1003: 2003" "\n"
4453  "");
4454  // and now let's deserialize to this variable.
4455  // first from a node:
4456  {
4457  my_type roundtrip;
4458  root_node.load(&roundtrip); // picks the node read(), because we wrote one.
4459  // if we didn't, it would then pick the tree read()
4460  roundtrip.check_eq(orig); // finally, let's compare.
4461  }
4462  // let's also deserialize from the tree:
4463  {
4464  my_type roundtrip;
4465  tree.load(root_id, &roundtrip); // will pick the tree read()
4466  roundtrip.check_eq(orig); // finally, let's compare.
4467  }
4468 
4469  // We created above a my_seq<std::string> specialization showing
4470  // that we can use write() without serializing the data to the
4471  // tree's arena. Let's show it working here:
4472  const my_seq_type<std::string> strseq{{
4473  "doe",
4474  "a deer, a female deer",
4475  "ray",
4476  "a drop of golden sun"
4477  }};
4478  // serialize it to a nested node in the tree:
4479  tree["not in arena"].save(strseq);
4480  // check the YAML:
4481  CHECK(ryml::emitrs_yaml<std::string>(tree["not in arena"]) == ""
4482  // note the new elements:
4483  "not in arena:" "\n"
4484  " - doe" "\n"
4485  " - a deer, a female deer" "\n"
4486  " - ray" "\n"
4487  " - a drop of golden sun" "\n"
4488  "");
4489  // show how the strings are NOT in the tree's arena
4490  size_t pos = 0;
4491  CHECK(strseq.seq_member.size() == tree["not in arena"].num_children());
4492  for(ryml::ConstNodeRef child : tree["not in arena"].children())
4493  {
4494  ryml::csubstr str_orig = ryml::to_csubstr(strseq.seq_member[pos++]);
4495  CHECK(child.val() == str_orig); // same string
4496  CHECK(!child.val().is_sub(tree.arena())); // not in the tree's arena
4497  CHECK(child.val().is_sub(str_orig)); // ... but the original memory
4498  }
4499 }
void load(id_type node, T *v, bool check_readable=true) const
(1) deserialize the node's contents (val or container) to the given variable, forwarding to the user-...
Definition: tree.hpp:871
void load(T *v, bool check_readable=true) const
(1) deserialize the node's contents (val or container) to the given variable, forwarding to the user-...
Definition: node.hpp:345
void check_eq(my_type const &that) const
csubstr arena() const
get the current arena
Definition: tree.hpp:1317
id_type root_id() const
Get the id of the root node. The tree must not be empty. The tree can be empty only when constructed ...
Definition: tree.hpp:386
A reference to a node in an existing yaml tree, offering a more convenient API than the index-based A...
Definition: node.hpp:1062
RYML_ID_TYPE id_type
The type of a node id in the YAML tree; to override the default type, define the macro RYML_ID_TYPE t...
Definition: common.hpp:124
id_type num_children(id_type node) const
O(num_children)
Definition: tree.cpp:1216
void save(T const &k)
Definition: node.hpp:1337
csubstr to_csubstr(const char(&s)[N]) noexcept
Definition: substr.hpp:2380
#define CHECK(predicate)
a testing assertion, used only in this quickstart
Definition: quickstart.cpp:310
Holds a pointer to an existing tree, and a node id.
Definition: node.hpp:736
example user container type with nested user types.
example user container type: seq-like
void save(id_type node, T const &val)
Definition: tree.hpp:772
void sample_std_types ( )

serialize/deserialize STL containers

demonstrates usage with the std implementations provided by ryml in the ryml_std.hpp header

See also
Serialize container types
also the STL section in Serialization/deserialization

Definition at line 4563 of file quickstart.cpp.

Referenced by main().

4564 {
4565  // we're using C-strings because doxygen breaks down on raw strings
4566  std::string yml_std_string = ""
4567  "- v2: (20,21)" "\n"
4568  " v3: (30,31,32)" "\n"
4569  " v4: (40,41,42,43)" "\n"
4570  " seq:" "\n"
4571  " - 101" "\n"
4572  " - 102" "\n"
4573  " - 103" "\n"
4574  " - 104" "\n"
4575  " - 105" "\n"
4576  " - 106" "\n"
4577  " - 107" "\n"
4578  " map:" "\n"
4579  " 1001: 2001" "\n"
4580  " 1002: 2002" "\n"
4581  " 1003: 2003" "\n"
4582  "- v2: (120,121)" "\n"
4583  " v3: (130,131,132)" "\n"
4584  " v4: (140,141,142,143)" "\n"
4585  " seq:" "\n"
4586  " - 1101" "\n"
4587  " - 1102" "\n"
4588  " - 1103" "\n"
4589  " - 1104" "\n"
4590  " - 1105" "\n"
4591  " - 1106" "\n"
4592  " - 1107" "\n"
4593  " map:" "\n"
4594  " 11001: 12001" "\n"
4595  " 11002: 12002" "\n"
4596  " 11003: 12003" "\n"
4597  "- v2: (220,221)" "\n"
4598  " v3: (230,231,232)" "\n"
4599  " v4: (240,241,242,243)" "\n"
4600  " seq:" "\n"
4601  " - 2101" "\n"
4602  " - 2102" "\n"
4603  " - 2103" "\n"
4604  " - 2104" "\n"
4605  " - 2105" "\n"
4606  " - 2106" "\n"
4607  " - 2107" "\n"
4608  " map:" "\n"
4609  " 21001: 22001" "\n"
4610  " 21002: 22002" "\n"
4611  " 21003: 22003" "\n"
4612  "";
4613  // parse in-place using the std::string above
4614  ryml::Tree tree = ryml::parse_in_place(ryml::to_substr(yml_std_string));
4615  // my_type is a container-of-containers type. see above its
4616  // definition implementation for ryml.
4617  std::vector<my_type> vec;
4618  tree.rootref().load(&vec);
4619  CHECK(vec.size() == 3);
4620  ryml::Tree tree_out;
4621  tree_out.rootref().save(vec);
4622  CHECK(ryml::emitrs_yaml<std::string>(tree_out) == yml_std_string);
4623 }
substr to_substr(char(&s)[N]) noexcept
Definition: substr.hpp:2376
void load(T *v, bool check_readable=true) const
(1) deserialize the node's contents (val or container) to the given variable, forwarding to the user-...
Definition: node.hpp:345
void save(T const &k)
Definition: node.hpp:1337
#define CHECK(predicate)
a testing assertion, used only in this quickstart
Definition: quickstart.cpp:310
NodeRef rootref()
Get the root as a NodeRef . Note that a non-const Tree implicitly converts to NodeRef.
Definition: tree.cpp:56
void parse_in_place(Parser *parser, csubstr filename, substr yaml, Tree *tree, id_type node_id)
(1) parse YAML into an existing tree node.
Definition: parse.cpp:165
void sample_deserialize_error ( )

shows error on deserializing nested nodes

shows what happens on deserialization errors

Note
read the doxygen documentation for containers/general types at: https://rapidyaml.readthedocs.io/v0.16.0/doxygen/group__doc__serialization__user__types.html
See also
sample_user_container_types()
sample_error_visit_location()
sample_location_tracking()

Definition at line 4513 of file quickstart.cpp.

Referenced by main().

4514 {
4515  // in this example we will be checking errors, so set up a
4516  // temporary error handler to catch them:
4517  ScopedErrorHandlerExample errh; // calls ryml::set_callbacks()
4518  // let's parse this YAML containing an invalid value,
4519  // with location tracking:
4520  ryml::EventHandlerTree tree_handler;
4521  ryml::Parser parser(&tree_handler, ryml::ParserOptions{}.locations(true));
4522  const ryml::Tree tree = parse_in_arena(&parser,
4523  "v2: (20,21)" "\n"
4524  "v3: (30,31,32)" "\n"
4525  "v4: (40,41,42,43)" "\n"
4526  "seq:" "\n"
4527  " - 101" "\n"
4528  " - 102" "\n"
4529  " - 103" "\n"
4530  " - 104" "\n"
4531  " - 105" "\n"
4532  " - 106" "\n"
4533  " - 107" "\n"
4534  "map:" "\n"
4535  " 1001: 2001" "\n"
4536  " 1002: not an int" "\n" // valid YAML, will cause deserialization error
4537  " 1003: 2003" "\n"
4538  "");
4539  ryml::ConstNodeRef root = tree;
4540  // and now let's deserialize to this variable
4541  my_type var;
4542  // .deserialize() reports the error but does not trigger an error
4543  // call:
4544  ryml::ReadResult result = root.deserialize(&var);
4545  CHECK(!result);
4546  CHECK(result.node == tree["map"]["1002"].id()); // this is where the error occurred
4547  CHECK(tree.location(parser, result.node).line == 13);
4548  CHECK(tree.location(parser, result.node).col == 2); // this node starts a column 2
4549  CHECK(parser.val_location(tree.val(result.node).str).col == 8); // its value starts at column 8
4550  // and .load() will trigger a visit error, reporting the node as
4551  // well. see sample_error_visit_location() for examples on how to
4552  // track the location of a visit error
4553  errh.check_error_occurs([&]{ root.load(&var); });
4554 }
A lightweight truthy type, used to enable reporting the offending node when a deserializing error hap...
Definition: common.hpp:161
Shows how to create a scoped error handler.
Definition: quickstart.cpp:368
The event handler to create a ryml Tree.
void load(T *v, bool check_readable=true) const
(1) deserialize the node's contents (val or container) to the given variable, forwarding to the user-...
Definition: node.hpp:345
ReadResult deserialize(T *v) const
(1) deserialize the node's contents (val or container) to the given variable, forwarding to the user-...
Definition: node.hpp:428
void parse_in_arena(Parser *parser, csubstr filename, csubstr yaml, Tree *tree, id_type node_id)
(1) parse YAML into an existing tree node. The filename will be used in any error messages arising du...
Definition: parse.cpp:209
Options to give to the ParseEngine to control its behavior.
Parser parser
Definition: parse.cpp:117
#define CHECK(predicate)
a testing assertion, used only in this quickstart
Definition: quickstart.cpp:310
Holds a pointer to an existing tree, and a node id.
Definition: node.hpp:736
ParserOptions & locations(bool enabled) noexcept
enable/disable source location tracking.
example user container type with nested user types.
This is the main driver of parsing logic: it scans the YAML or JSON source for tokens, and emits the appropriate sequence of parsing events to its event handler.
Definition: fwd.hpp:17
bool check_error_occurs(Fn &&fn)
checking that an error occurs while calling fn
void sample_float_precision ( )

control precision of serialized floats

Definition at line 4629 of file quickstart.cpp.

Referenced by main().

4630 {
4631  std::vector<double> reference{1.23234412342131234, 2.12323123143434237, 3.67847983572591234};
4632  // A safe precision for comparing doubles. May vary depending on
4633  // compiler flags. Double goes to about 15 digits, so 14 should be
4634  // safe enough for this test to succeed.
4635  const double precision_safe = 1.e-14;
4636  const size_t num_digits_safe = 14;
4637  const size_t num_digits_original = 17;
4638  auto get_num_digits = [](ryml::csubstr number){ return number.len - 2u; };
4639  //
4640  // no significant precision is lost when reading
4641  // floating point numbers:
4642  {
4643  ryml::Tree tree = ryml::parse_in_arena("[1.23234412342131234, 2.12323123143434237, 3.67847983572591234]");
4644  std::vector<double> output;
4645  tree.rootref().load(&output);
4646  CHECK(output.size() == reference.size());
4647  for(size_t i = 0; i < reference.size(); ++i)
4648  {
4649  CHECK(get_num_digits(tree[(ryml::id_type)i].val()) == num_digits_original);
4650  CHECK(fabs(output[i] - reference[i]) < precision_safe);
4651  }
4652  }
4653  //
4654  // However, depending on the compilation settings, there may be a
4655  // significant precision loss when serializing with the default
4656  // approach, .save(double):
4657  {
4658  ryml::Tree serialized;
4659  serialized.rootref().save(reference);
4660  // Without std::to_chars() there is a loss of precision:
4661  #if (!C4CORE_HAVE_STD_TOCHARS) // check if std::to_chars() is available.
4662  CHECK((ryml::emitrs_yaml<std::string>(serialized) == ""
4663  "- 1.23234" "\n"
4664  "- 2.12323" "\n"
4665  "- 3.67848" "\n"
4666  "") || (bool)"this is indicative; the exact results will vary from platform to platform.");
4667  C4_UNUSED(num_digits_safe);
4668  // ... but when using C++17 and above, the results are eminently equal:
4669  #else
4670  CHECK((ryml::emitrs_yaml<std::string>(serialized) == ""
4671  "- 1.2323441234213124" "\n"
4672  "- 2.1232312314343424" "\n"
4673  "- 3.6784798357259123" "\n"
4674  "") || (bool)"this is indicative; the exact results will vary from platform to platform.");
4675  size_t pos = 0;
4676  for(ryml::ConstNodeRef child : serialized.rootref().children())
4677  {
4678  CHECK(get_num_digits(child.val()) >= num_digits_safe);
4679  double out = {};
4680  child.load(&out);
4681  CHECK(fabs(out - reference[pos++]) < precision_safe);
4682  }
4683  #endif
4684  }
4685  //
4686  // The difference is explained by the availability of
4687  // fastfloat::from_chars(), std::from_chars() and std::to_chars().
4688  //
4689  // ryml prefers the fastfloat::from_chars() version. Unfortunately
4690  // fastfloat does not have to_chars() (see
4691  // https://github.com/fastfloat/fast_float/issues/23).
4692  //
4693  // When C++17 is used, ryml uses std::to_chars(), which produces
4694  // good defaults.
4695  //
4696  // However, with earlier standards, or in some library
4697  // implementations, there's only snprintf() available. Every other
4698  // std library function will either disrespect the string limits,
4699  // or more precisely, accept no string size limits. So the
4700  // implementation of c4core (which ryml uses) falls back to
4701  // snprintf("%g"), and that picks by default a (low) number of
4702  // digits.
4703  //
4704  // But not all is lost for C++11/C++14 users!
4705  //
4706  // To force a particular precision when serializing, you can use
4707  // c4::fmt::real() (brought into the ryml:: namespace). Or you can
4708  // serialize the number yourself! The small downside is that you
4709  // have to build the container.
4710  //
4711  // First a function to check the result:
4712  auto check_precision = [&](ryml::Tree const& serialized){
4713  std::cout << serialized;
4714  // now it works!
4715  CHECK((ryml::emitrs_yaml<std::string>(serialized) == ""
4716  "- 1.23234412342131239" "\n"
4717  "- 2.12323123143434245" "\n"
4718  "- 3.67847983572591231" "\n"
4719  "") || (bool)"this is indicative; the exact results will vary from platform to platform.");
4720  size_t pos = 0;
4721  for(ryml::ConstNodeRef child : serialized.rootref().children())
4722  {
4723  CHECK(get_num_digits(child.val()) == num_digits_original);
4724  double out = {};
4725  child.load(&out);
4726  CHECK(fabs(out - reference[pos++]) < precision_safe);
4727  }
4728  };
4729  //
4730  // Serialization example using fmt::real()
4731  {
4732  ryml::Tree serialized;
4733  ryml::NodeRef root = serialized.rootref();
4734  root.set_seq();
4735  for(const double v : reference)
4736  root.append_child().save(ryml::fmt::real(v, num_digits_original, ryml::FTOA_FLOAT));
4737  check_precision(serialized); // OK - now within bounds!
4738  }
4739  //
4740  // Serialization example using snprintf
4741  {
4742  ryml::Tree serialized;
4743  ryml::NodeRef root = serialized.rootref();
4744  root.set_seq();
4745  char tmp[64];
4746  for(const double v : reference)
4747  {
4748  // reuse a buffer to serialize.
4749  // add 1 to the significant digits because the %g
4750  // specifier counts the integral digits.
4751  (void)snprintf(tmp, sizeof(tmp), "%.18g", v);
4752  // copy the serialized string to the tree (.save()
4753  // copies to the arena, .set_val() just assigns the string
4754  // pointer and would be wrong in this case):
4755  root.append_child().save(ryml::to_csubstr((const char*)tmp));
4756  }
4757  check_precision(serialized); // OK - now within bounds!
4758  }
4759 }
children_view children() RYML_NOEXCEPT
get an iterable view over children
Definition: node.hpp:1843
void load(T *v, bool check_readable=true) const
(1) deserialize the node's contents (val or container) to the given variable, forwarding to the user-...
Definition: node.hpp:345
print the real number in floating point format (like f)
Definition: charconv.hpp:194
real_< T > real(T val, int precision, RealFormat_e fmt=FTOA_FLOAT)
Definition: format.hpp:362
void parse_in_arena(Parser *parser, csubstr filename, csubstr yaml, Tree *tree, id_type node_id)
(1) parse YAML into an existing tree node. The filename will be used in any error messages arising du...
Definition: parse.cpp:209
A reference to a node in an existing yaml tree, offering a more convenient API than the index-based A...
Definition: node.hpp:1062
RYML_ID_TYPE id_type
The type of a node id in the YAML tree; to override the default type, define the macro RYML_ID_TYPE t...
Definition: common.hpp:124
void save(T const &k)
Definition: node.hpp:1337
csubstr to_csubstr(const char(&s)[N]) noexcept
Definition: substr.hpp:2380
#define CHECK(predicate)
a testing assertion, used only in this quickstart
Definition: quickstart.cpp:310
Holds a pointer to an existing tree, and a node id.
Definition: node.hpp:736
NodeRef rootref()
Get the root as a NodeRef . Note that a non-const Tree implicitly converts to NodeRef.
Definition: tree.cpp:56
const_children_view children() const RYML_NOEXCEPT
get an iterable view over children
Definition: node.hpp:987
NodeRef append_child()
Definition: node.hpp:1715
void set_seq()
Definition: node.hpp:1254