rapidyaml 0.15.2
parse and emit YAML, and do it fast
Loading...
Searching...
No Matches
Serialization

Functions

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_serialize_basic ()
 serialize/deserialize fundamental types
void sample_user_scalar_types ()
 serialize/deserialize scalar (leaf/scalar) types
void sample_user_container_types ()
 serialize/deserialize container (map or seq) types
void sample_std_types ()
 serialize/deserialize STL containers
void sample_deserialize_error ()
 shows error on deserializing nested nodes
void sample_float_precision ()
 control precision of serialized floats

Detailed Description

Function Documentation

◆ sample_fundamental_types()

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 3015 of file quickstart.cpp.

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

Referenced by main().

◆ sample_empty_null_values()

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 3107 of file quickstart.cpp.

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

Referenced by main().

◆ sample_formatting()

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 3278 of file quickstart.cpp.

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

Referenced by main().

◆ sample_base64()

void sample_base64 ( )

encode/decode base64

demonstrates how to read and write base64-encoded blobs.

See also
Base64 encoding/decoding

Definition at line 3704 of file quickstart.cpp.

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

Referenced by main().

◆ sample_serialize_basic()

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 3872 of file quickstart.cpp.

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

Referenced by main().

◆ sample_user_scalar_types()

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.

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

See Serialize/deserialize scalar types.

Definition at line 4016 of file quickstart.cpp.

4017{
4018 ryml::Tree t;
4019
4020 auto r = t.rootref();
4021 r.set_map();
4022
4023 vec2<int> v2in{10, 11};
4024 vec2<int> v2out{1, 2};
4025 r["v2"].save(v2in); // serializes to the tree's arena, and then sets the val
4026 r["v2"].load(&v2out);
4027 CHECK(v2in.x == v2out.x);
4028 CHECK(v2in.y == v2out.y);
4029 vec3<int> v3in{100, 101, 102};
4030 vec3<int> v3out{1, 2, 3};
4031 r["v3"].save(v3in); // serializes to the tree's arena, and then sets the val
4032 r["v3"].load(&v3out);
4033 CHECK(v3in.x == v3out.x);
4034 CHECK(v3in.y == v3out.y);
4035 CHECK(v3in.z == v3out.z);
4036 vec4<int> v4in{1000, 1001, 1002, 1003};
4037 vec4<int> v4out{1, 2, 3, 4};
4038 r["v4"].save(v4in); // serializes to the tree's arena, and then sets the val
4039 r["v4"].load(&v4out);
4040 CHECK(v4in.x == v4out.x);
4041 CHECK(v4in.y == v4out.y);
4042 CHECK(v4in.z == v4out.z);
4043 CHECK(v4in.w == v4out.w);
4045 "v2: (10,11)" "\n"
4046 "v3: (100,101,102)" "\n"
4047 "v4: (1000,1001,1002,1003)" "\n"
4048 "");
4049
4050 // note that only the used functions are needed:
4051 // - if a type is only parsed, then only from_chars() is needed
4052 // - if a type is only emitted, then only to_chars() is needed
4053 emit_only_vec2<int> eov2in{20, 21}; // only has to_chars()
4054 parse_only_vec2<int> pov2out{1, 2}; // only has from_chars()
4055 r["v2"].save(eov2in); // serializes to the tree's arena, and then sets the keyval
4056 r["v2"].load(&pov2out);
4057 CHECK(eov2in.x == pov2out.x);
4058 CHECK(eov2in.y == pov2out.y);
4059 emit_only_vec3<int> eov3in{30, 31, 32}; // only has to_chars()
4060 parse_only_vec3<int> pov3out{1, 2, 3}; // only has from_chars()
4061 r["v3"].save(eov3in); // serializes to the tree's arena, and then sets the keyval
4062 r["v3"].load(&pov3out);
4063 CHECK(eov3in.x == pov3out.x);
4064 CHECK(eov3in.y == pov3out.y);
4065 CHECK(eov3in.z == pov3out.z);
4066 emit_only_vec4<int> eov4in{40, 41, 42, 43}; // only has to_chars()
4067 parse_only_vec4<int> pov4out{1, 2, 3, 4}; // only has from_chars()
4068 r["v4"].save(eov4in); // serializes to the tree's arena, and then sets the keyval
4069 r["v4"].load(&pov4out);
4070 CHECK(eov4in.x == pov4out.x);
4071 CHECK(eov4in.y == pov4out.y);
4072 CHECK(eov4in.z == pov4out.z);
4074 "v2: (20,21)" "\n"
4075 "v3: (30,31,32)" "\n"
4076 "v4: (40,41,42,43)" "\n"
4077 "");
4078}
example scalar type, serialized only
example scalar type, serialized only
example scalar type, serialized only
example scalar type, deserialized only
example scalar type, deserialized only
example scalar type, deserialized only
example scalar type, serialized and deserialized
example scalar type, serialized and deserialized
example scalar type, serialized and deserialized

Referenced by main().

◆ sample_user_container_types()

void sample_user_container_types ( )

serialize/deserialize container (map or seq) types

shows how to serialize/deserialize container types.

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

Definition at line 4330 of file quickstart.cpp.

4331{
4332 // let's do a YAML roundtrip:
4333 ryml::Tree tree;
4334 ryml::NodeRef root_node = tree;
4335 ryml::id_type root_id = tree.root_id();
4336
4337 // here we will be doing a serialization roundtrip with a
4338 // user-defined container type.
4339 //
4340 // the read() and write() functions for this type are defined
4341 // above.
4342 const my_type orig{
4343 {20, 21},
4344 {30, 31, 32},
4345 {40, 41, 42, 43},
4346 {{101, 102, 103, 104, 105, 106, 107}},
4347 {{{1001, 2001}, {1002, 2002}, {1003, 2003}}},
4348 };
4349
4350 // serialize to the tree:
4351 root_node.save(orig);
4352 // check the YAML:
4354 "v2: (20,21)" "\n"
4355 "v3: (30,31,32)" "\n"
4356 "v4: (40,41,42,43)" "\n"
4357 "seq:" "\n"
4358 " - 101" "\n"
4359 " - 102" "\n"
4360 " - 103" "\n"
4361 " - 104" "\n"
4362 " - 105" "\n"
4363 " - 106" "\n"
4364 " - 107" "\n"
4365 "map:" "\n"
4366 " 1001: 2001" "\n"
4367 " 1002: 2002" "\n"
4368 " 1003: 2003" "\n"
4369 "");
4370 // and now let's deserialize to this variable.
4371 // first from a node:
4372 {
4373 my_type roundtrip;
4374 root_node.load(&roundtrip); // picks the node read(), because we wrote one.
4375 // if we didn't, it would then pick the tree read()
4376 roundtrip.check_eq(orig); // finally, let's compare.
4377 }
4378 // let's also deserialize from the tree:
4379 {
4380 my_type roundtrip;
4381 tree.load(root_id, &roundtrip); // will pick the tree read()
4382 roundtrip.check_eq(orig); // finally, let's compare.
4383 }
4384
4385 // We created above a my_seq<std::string> specialization showing
4386 // that we can use write() without serializing the data to the
4387 // tree's arena. Let's show it working here:
4388 const my_seq_type<std::string> strseq{{
4389 "doe",
4390 "a deer, a female deer",
4391 "ray",
4392 "a drop of golden sun"
4393 }};
4394 // serialize it to a nested node in the tree:
4395 tree["not in arena"].save(strseq);
4396 // check the YAML:
4397 CHECK(ryml::emitrs_yaml<std::string>(tree["not in arena"]) == ""
4398 // note the new elements:
4399 "not in arena:" "\n"
4400 " - doe" "\n"
4401 " - a deer, a female deer" "\n"
4402 " - ray" "\n"
4403 " - a drop of golden sun" "\n"
4404 "");
4405 // show how the strings are NOT in the tree's arena
4406 size_t pos = 0;
4407 CHECK(strseq.seq_member.size() == tree["not in arena"].num_children());
4408 for(ryml::ConstNodeRef child : tree["not in arena"].children())
4409 {
4410 ryml::csubstr str_orig = ryml::to_csubstr(strseq.seq_member[pos++]);
4411 CHECK(child.val() == str_orig); // same string
4412 CHECK(!child.val().is_sub(tree.arena())); // not in the tree's arena
4413 CHECK(child.val().is_sub(str_orig)); // ... but the original memory
4414 }
4415}
void save(T const &k)
Definition node.hpp:1337
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
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
example user container type: seq-like
std::vector< T > seq_member
example user container type with nested user types.
void check_eq(my_type const &that) const

Referenced by main().

◆ sample_std_types()

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/deserialize container types
also the STL section in Serialization/deserialization

Definition at line 4479 of file quickstart.cpp.

4480{
4481 // we're using C-strings because doxygen breaks down on raw strings
4482 std::string yml_std_string = ""
4483 "- v2: (20,21)" "\n"
4484 " v3: (30,31,32)" "\n"
4485 " v4: (40,41,42,43)" "\n"
4486 " seq:" "\n"
4487 " - 101" "\n"
4488 " - 102" "\n"
4489 " - 103" "\n"
4490 " - 104" "\n"
4491 " - 105" "\n"
4492 " - 106" "\n"
4493 " - 107" "\n"
4494 " map:" "\n"
4495 " 1001: 2001" "\n"
4496 " 1002: 2002" "\n"
4497 " 1003: 2003" "\n"
4498 "- v2: (120,121)" "\n"
4499 " v3: (130,131,132)" "\n"
4500 " v4: (140,141,142,143)" "\n"
4501 " seq:" "\n"
4502 " - 1101" "\n"
4503 " - 1102" "\n"
4504 " - 1103" "\n"
4505 " - 1104" "\n"
4506 " - 1105" "\n"
4507 " - 1106" "\n"
4508 " - 1107" "\n"
4509 " map:" "\n"
4510 " 11001: 12001" "\n"
4511 " 11002: 12002" "\n"
4512 " 11003: 12003" "\n"
4513 "- v2: (220,221)" "\n"
4514 " v3: (230,231,232)" "\n"
4515 " v4: (240,241,242,243)" "\n"
4516 " seq:" "\n"
4517 " - 2101" "\n"
4518 " - 2102" "\n"
4519 " - 2103" "\n"
4520 " - 2104" "\n"
4521 " - 2105" "\n"
4522 " - 2106" "\n"
4523 " - 2107" "\n"
4524 " map:" "\n"
4525 " 21001: 22001" "\n"
4526 " 21002: 22002" "\n"
4527 " 21003: 22003" "\n"
4528 "";
4529 // parse in-place using the std::string above
4530 ryml::Tree tree = ryml::parse_in_place(ryml::to_substr(yml_std_string));
4531 // my_type is a container-of-containers type. see above its
4532 // definition implementation for ryml.
4533 std::vector<my_type> vec;
4534 tree.rootref().load(&vec);
4535 CHECK(vec.size() == 3);
4536 ryml::Tree tree_out;
4537 tree_out.rootref().save(vec);
4538 CHECK(ryml::emitrs_yaml<std::string>(tree_out) == yml_std_string);
4539}
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

Referenced by main().

◆ sample_deserialize_error()

void sample_deserialize_error ( )

shows error on deserializing nested nodes

shows what happens on deserialization errors

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

Definition at line 4429 of file quickstart.cpp.

4430{
4431 // in this example we will be checking errors, so set up a
4432 // temporary error handler to catch them:
4433 ScopedErrorHandlerExample errh; // calls ryml::set_callbacks()
4434 // let's parse this YAML containing an invalid value,
4435 // with location tracking:
4436 ryml::EventHandlerTree tree_handler;
4437 ryml::Parser parser(&tree_handler, ryml::ParserOptions{}.locations(true));
4438 const ryml::Tree tree = parse_in_arena(&parser,
4439 "v2: (20,21)" "\n"
4440 "v3: (30,31,32)" "\n"
4441 "v4: (40,41,42,43)" "\n"
4442 "seq:" "\n"
4443 " - 101" "\n"
4444 " - 102" "\n"
4445 " - 103" "\n"
4446 " - 104" "\n"
4447 " - 105" "\n"
4448 " - 106" "\n"
4449 " - 107" "\n"
4450 "map:" "\n"
4451 " 1001: 2001" "\n"
4452 " 1002: not an int" "\n" // valid YAML, will cause deserialization error
4453 " 1003: 2003" "\n"
4454 "");
4455 ryml::ConstNodeRef root = tree;
4456 // and now let's deserialize to this variable
4457 my_type var;
4458 // .deserialize() reports the error but does not trigger an error
4459 // call:
4460 ryml::ReadResult result = root.deserialize(&var);
4461 CHECK(!result);
4462 CHECK(result.node == tree["map"]["1002"].id()); // this is where the error occurred
4463 CHECK(tree.location(parser, result.node).line == 13);
4464 CHECK(tree.location(parser, result.node).col == 2); // this node starts a column 2
4465 CHECK(parser.val_location(tree.val(result.node).str).col == 8); // its value starts at column 8
4466 // and .load() will trigger a visit error, reporting the node as
4467 // well. see sample_error_visit_location() for examples on how to
4468 // track the location of a visit error
4469 errh.check_error_occurs([&]{ root.load(&var); });
4470}
csubstr const & val(id_type node) const
Definition tree.hpp:460
Location location(Parser const &p, id_type node) const
Get the location of a node from the parse used to parse this tree.
Definition tree.cpp:1913
ParseEngine< EventHandlerTree > Parser
This is the main ryml parser, where the parser events are handled to create a ryml tree (see Event Ha...
Definition fwd.hpp:19
The event handler to create a ryml Tree.
size_t col
column
Definition common.hpp:232
size_t line
line
Definition common.hpp:231
Options to give to the ParseEngine to control its behavior.
ParserOptions & locations(bool enabled) noexcept
enable/disable source location tracking.
A lightweight truthy type, used to enable reporting the offending node when a deserializing error hap...
Definition common.hpp:162
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

Referenced by main().

◆ sample_float_precision()

void sample_float_precision ( )

control precision of serialized floats

Definition at line 4545 of file quickstart.cpp.

4546{
4547 std::vector<double> reference{1.23234412342131234, 2.12323123143434237, 3.67847983572591234};
4548 // A safe precision for comparing doubles. May vary depending on
4549 // compiler flags. Double goes to about 15 digits, so 14 should be
4550 // safe enough for this test to succeed.
4551 const double precision_safe = 1.e-14;
4552 const size_t num_digits_safe = 14;
4553 const size_t num_digits_original = 17;
4554 auto get_num_digits = [](ryml::csubstr number){ return number.len - 2u; };
4555 //
4556 // no significant precision is lost when reading
4557 // floating point numbers:
4558 {
4559 ryml::Tree tree = ryml::parse_in_arena("[1.23234412342131234, 2.12323123143434237, 3.67847983572591234]");
4560 std::vector<double> output;
4561 tree.rootref().load(&output);
4562 CHECK(output.size() == reference.size());
4563 for(size_t i = 0; i < reference.size(); ++i)
4564 {
4565 CHECK(get_num_digits(tree[(ryml::id_type)i].val()) == num_digits_original);
4566 CHECK(fabs(output[i] - reference[i]) < precision_safe);
4567 }
4568 }
4569 //
4570 // However, depending on the compilation settings, there may be a
4571 // significant precision loss when serializing with the default
4572 // approach, .save(double):
4573 {
4574 ryml::Tree serialized;
4575 serialized.rootref().save(reference);
4576 // Without std::to_chars() there is a loss of precision:
4577 #if (!C4CORE_HAVE_STD_TOCHARS) // check if std::to_chars() is available.
4578 CHECK((ryml::emitrs_yaml<std::string>(serialized) == ""
4579 "- 1.23234" "\n"
4580 "- 2.12323" "\n"
4581 "- 3.67848" "\n"
4582 "") || (bool)"this is indicative; the exact results will vary from platform to platform.");
4583 C4_UNUSED(num_digits_safe);
4584 // ... but when using C++17 and above, the results are eminently equal:
4585 #else
4586 CHECK((ryml::emitrs_yaml<std::string>(serialized) == ""
4587 "- 1.2323441234213124" "\n"
4588 "- 2.1232312314343424" "\n"
4589 "- 3.6784798357259123" "\n"
4590 "") || (bool)"this is indicative; the exact results will vary from platform to platform.");
4591 size_t pos = 0;
4592 for(ryml::ConstNodeRef child : serialized.rootref().children())
4593 {
4594 CHECK(get_num_digits(child.val()) >= num_digits_safe);
4595 double out = {};
4596 child.load(&out);
4597 CHECK(fabs(out - reference[pos++]) < precision_safe);
4598 }
4599 #endif
4600 }
4601 //
4602 // The difference is explained by the availability of
4603 // fastfloat::from_chars(), std::from_chars() and std::to_chars().
4604 //
4605 // ryml prefers the fastfloat::from_chars() version. Unfortunately
4606 // fastfloat does not have to_chars() (see
4607 // https://github.com/fastfloat/fast_float/issues/23).
4608 //
4609 // When C++17 is used, ryml uses std::to_chars(), which produces
4610 // good defaults.
4611 //
4612 // However, with earlier standards, or in some library
4613 // implementations, there's only snprintf() available. Every other
4614 // std library function will either disrespect the string limits,
4615 // or more precisely, accept no string size limits. So the
4616 // implementation of c4core (which ryml uses) falls back to
4617 // snprintf("%g"), and that picks by default a (low) number of
4618 // digits.
4619 //
4620 // But not all is lost for C++11/C++14 users!
4621 //
4622 // To force a particular precision when serializing, you can use
4623 // c4::fmt::real() (brought into the ryml:: namespace). Or you can
4624 // serialize the number yourself! The small downside is that you
4625 // have to build the container.
4626 //
4627 // First a function to check the result:
4628 auto check_precision = [&](ryml::Tree const& serialized){
4629 std::cout << serialized;
4630 // now it works!
4631 CHECK((ryml::emitrs_yaml<std::string>(serialized) == ""
4632 "- 1.23234412342131239" "\n"
4633 "- 2.12323123143434245" "\n"
4634 "- 3.67847983572591231" "\n"
4635 "") || (bool)"this is indicative; the exact results will vary from platform to platform.");
4636 size_t pos = 0;
4637 for(ryml::ConstNodeRef child : serialized.rootref().children())
4638 {
4639 CHECK(get_num_digits(child.val()) == num_digits_original);
4640 double out = {};
4641 child.load(&out);
4642 CHECK(fabs(out - reference[pos++]) < precision_safe);
4643 }
4644 };
4645 //
4646 // Serialization example using fmt::real()
4647 {
4648 ryml::Tree serialized;
4649 ryml::NodeRef root = serialized.rootref();
4650 root.set_seq();
4651 for(const double v : reference)
4652 root.append_child().save(ryml::fmt::real(v, num_digits_original, ryml::FTOA_FLOAT));
4653 check_precision(serialized); // OK - now within bounds!
4654 }
4655 //
4656 // Serialization example using snprintf
4657 {
4658 ryml::Tree serialized;
4659 ryml::NodeRef root = serialized.rootref();
4660 root.set_seq();
4661 char tmp[64];
4662 for(const double v : reference)
4663 {
4664 // reuse a buffer to serialize.
4665 // add 1 to the significant digits because the %g
4666 // specifier counts the integral digits.
4667 (void)snprintf(tmp, sizeof(tmp), "%.18g", v);
4668 // copy the serialized string to the tree (.save()
4669 // copies to the arena, .set_val() just assigns the string
4670 // pointer and would be wrong in this case):
4671 root.append_child().save(ryml::to_csubstr((const char*)tmp));
4672 }
4673 check_precision(serialized); // OK - now within bounds!
4674 }
4675}
const_children_view children() const RYML_NOEXCEPT
get an iterable view over children
Definition node.hpp:987
children_view children() RYML_NOEXCEPT
get an iterable view over children
Definition node.hpp:1843

Referenced by main().