rapidyaml 0.16.0
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_brief ()
 serialize/deserialize container (map or seq) types: brief version
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 3017 of file quickstart.cpp.

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

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:
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:
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:
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:
3265 "empty_null: ~" "\n"
3266 "empty_nonnull: ''" "\n"
3267 "str_null: ~" "\n"
3268 "str_tilde: ~" "\n"
3269 "");
3270}
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 3280 of file quickstart.cpp.

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

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();
3724 ch.set_key_serialized(ryml::fmt::base64(c.text));
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:
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}
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 3874 of file quickstart.cpp.

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 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.

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.

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);
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);
4075 "v2: (20,21)" "\n"
4076 "v3: (30,31,32)" "\n"
4077 "v4: (40,41,42,43)" "\n"
4078 "");
4079}
void save(T const &k)
Definition node.hpp:1337
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_brief()

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.

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:
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}
serializes as a map

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 (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.

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

Definition at line 4563 of file quickstart.cpp.

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}
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

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.

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

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}
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().