• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

biojppm / rapidyaml / 18073302777

28 Sep 2025 10:57AM UTC coverage: 97.448% (+0.1%) from 97.312%
18073302777

Pull #536

github

web-flow
Merge 4d67b9f24 into 9a044c88b
Pull Request #536: Int handler

976 of 1001 new or added lines in 16 files covered. (97.5%)

2 existing lines in 1 file now uncovered.

13021 of 13362 relevant lines covered (97.45%)

765405.06 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.71
/tools/yaml_events.cpp
1
#ifdef RYML_SINGLE_HEADER
2
#include <ryml_all.hpp>
3
#else
4
#include <c4/yml/std/std.hpp>
5
#include <c4/yml/parse.hpp>
6
#include <c4/yml/event_handler_tree.hpp>
7
#include <c4/yml/parse_engine.def.hpp>
8
#endif
9
#include <c4/yml/extra/string.hpp>
10
#include <c4/yml/extra/scalar.hpp>
11
#include <c4/yml/extra/event_handler_ints.hpp>
12
#include <c4/yml/extra/event_handler_testsuite.hpp>
13
#include <c4/yml/extra/event_handler_ints_utils.hpp>
14
#include <testsuite/testsuite_events.hpp>
15
#include <c4/fs/fs.hpp>
16
#include <cstdio>
17
#include <chrono>
18

19

20
#ifdef C4_EXCEPTIONS
21
#include <stdexcept>
22
#else
23
#include <csetjmp>
24
std::jmp_buf jmp_env = {};
25
c4::csubstr jmp_msg = {};
26
#endif
27

28
C4_SUPPRESS_WARNING_GCC("-Wold-style-cast")
29

30

31
//-----------------------------------------------------------------------------
32

33
using namespace c4;
34
using namespace c4::yml;
35

36
enum class evts_type
37
{
38
    testsuite_src,
39
    testsuite_ints,
40
    testsuite_tree,
41
    ryml_ints,
42
};
43

44
struct Args
45
{
46
    csubstr filename = "-";
47
    evts_type evts = evts_type::testsuite_src;
48
    int ints_size = -1; // estimate by default
49
    bool ints_size_force = false; // do not force the estimated size
50
    static bool parse(Args *args, int argc, const char *argv[], int *errcode);
51
};
52

53
const char usage[] = R"(usage:
54

55
ryml-yaml-events <command> <options> [-]        # read from stdin (default)
56
ryml-yaml-events <command> <options> <file>     # read from file
57

58
The command must be one of the following:
59

60
    testsuite_src,ts_src,tss
61
         emit test suite events directly from source: parse the YAML
62
         source, and directly emit events during the parse (ie, no
63
         ryml tree is created). This is the default behavior when the
64
         option is omitted.
65

66
    testsuite_tree,ts_tree,tst
67
         emit test suite events from tree: parse the YAML source,
68
         creating a ryml tree. Once the tree is completely created,
69
         emit the test suite events by iterating over the nodes of the
70
         created tree.
71

72
    testsuite_ints,ts_ints,tsi
73
         emit test suite events from the ryml int events handler:
74
         parse the YAML source to a container of ryml int events. Once
75
         this is completed, emit the corresponding test suite events.
76

77
    ryml_ints,ri
78
         emit ryml int events: parse the YAML source to a container of
79
         ryml int events. Once this is completed, print those same int
80
         events.
81

82
The following options influence the behavior of the program:
83

84
    --timings,--timing,-t
85
         print task timings and size information (to stderr)
86

87
The following options influence the behavior of testsuite_ints and ryml_ints:
88

89
    --ints-size <int-number>,-is <int-number>
90
         when using int events, set the int event buffer size from this
91
         value. use a negative value to force a conservative estimation
92
         from a first run over the YAML source, and then multiply the
93
         estimation by the symmetrical of that value. For example, -2
94
         will result in 2*estimation. Default is -1.
95

96
    --ints-size-force,-isf
97
         when using int events, force the initial int event buffer size
98
         to prevail: if this size is not large enough to accomodate the
99
         actual number of events required from the YAML source, exit
100
         with a nonzero error code. This is in contrast to the default
101
         behavior, which consists of expanding the buffer as needed,
102
         which requires two parses and two string copies of the
103
         original source buffer.
104

105
EXAMPLES:
106

107
  $ ryml-yaml-events ts_src            # parse stdin to test suite events, then print the events
108
  $ ryml-yaml-events ts_src -          # parse stdin to test suite events, then print the events
109
  $ ryml-yaml-events ts_src <file>     # parse file to test suite events, then print the events
110

111
  $ ryml-yaml-events ts_tree           # parse stdin to ryml tree, emit test suite events from created tree
112
  $ ryml-yaml-events ts_tree -         # parse stdin to ryml tree, emit test suite events from created tree
113
  $ ryml-yaml-events ts_tree <file>    # parse file to ryml tree, emit test suite events from created tree
114

115
  $ ryml-yaml-events ts_ints           # parse stdin to ryml int events, emit test suite events from ryml int events
116
  $ ryml-yaml-events ts_ints -         # parse stdin to ryml int events, emit test suite events from ryml int events
117
  $ ryml-yaml-events ts_ints <file>    # parse file to ryml int events, emit test suite events from ryml int events
118

119
  $ ryml-yaml-events ryml_ints         # parse stdin to ryml int events, emit ryml int events
120
  $ ryml-yaml-events ryml_ints -       # parse stdin to ryml int events, emit ryml int events
121
  $ ryml-yaml-events ryml_ints <file>  # parse file to ryml int events, emit ryml int events
122

123
)";
124

125

126
//-----------------------------------------------------------------------------
127

128
using IntEvents = std::vector<extra::ievt::DataType>;
129

130
std::string load_file(csubstr filename);
131
extra::string emit_testsuite_events(csubstr filename, substr filecontents);
132
std::string emit_testsuite_events_from_tree(csubstr filename, substr filecontents);
133
std::string emit_testsuite_events_from_ints(csubstr filename, substr filecontents, IntEvents &evts, bool fail_size);
134
void emit_ints_events(csubstr filename, substr filecontents, IntEvents &evts, bool fail_size);
135
int estimate_ints_size(csubstr filecontents, int size);
136
Callbacks create_custom_callbacks();
137

138

139
bool timing_enabled = false;
140
double src_size = 0;
141
namespace stdc = std::chrono;
142
struct stopwatch
143
{
144
    using clock_type = stdc::steady_clock;
145
    const char *name;
146
    clock_type::time_point start;
147
    stopwatch(const char *name_)
640✔
148
    {
640✔
149
        if(!timing_enabled) return;
640✔
150
        name = name_;
56✔
151
        stack.emplace_back(this);
56✔
152
        start = clock_type::now();
56✔
153
    }
154
    ~stopwatch()
640✔
155
    {
56✔
156
        if(!timing_enabled) return;
640✔
157
        stdc::duration<double, std::milli> delta = clock_type::now() - start;
56✔
158
        for(stopwatch const* sw : stack)
176✔
159
            fprintf(stderr, "%s:", sw->name);
120✔
160
        fprintf(stderr, " %.6fms (%.3fMB/s)\n", delta.count(), src_size / delta.count() * 1.e-3);
56✔
161
        stack.resize(stack.size()-1);
56✔
162
    }
640✔
163
    static std::vector<stopwatch*> stack;
164

165
};
166
std::vector<stopwatch*> stopwatch::stack;
167
#define STOPWATCH(name) stopwatch C4_XCAT(timer, __LINE__){name}
168

169

170
//-----------------------------------------------------------------------------
171

172
int main(int argc, const char *argv[])
160✔
173
{
174
    Args args = {};
160✔
175
    {
176
        int errcode = 0;
160✔
177
        if(!Args::parse(&args, argc, argv, &errcode))
160✔
178
            return errcode;
48✔
179
    }
180
    set_callbacks(create_custom_callbacks());
112✔
181
    C4_IF_EXCEPTIONS_(try, if(setjmp(jmp_env) == 0))
182
    {
183
        std::string src;
112✔
184
        {
185
            STOPWATCH("load_file");
112✔
186
            src = load_file(args.filename);
112✔
187
            src_size = (double)src.size();
104✔
188
            if(timing_enabled) fprintf(stderr, "src_size=%zuB\n", src.size());
104✔
189
        }
112✔
190
        STOPWATCH("process");
104✔
191
        switch(args.evts)
104✔
192
        {
193
        case evts_type::testsuite_src:
32✔
194
        {
195
            extra::string evts;
32✔
196
            {
197
                STOPWATCH("testsuite_src");
32✔
198
                evts = emit_testsuite_events(args.filename, to_substr(src));
32✔
199
            }
32✔
200
            {
201
                STOPWATCH("print");
8✔
202
                std::fwrite(evts.data(), 1, evts.size(), stdout);
8✔
203
            }
8✔
204
            break;
8✔
205
        }
32✔
206
        case evts_type::testsuite_tree:
40✔
207
        {
208
            std::string evts;
40✔
209
            {
210
                STOPWATCH("testsuite_tree");
40✔
211
                evts = emit_testsuite_events_from_tree(args.filename, to_substr(src));
40✔
212
            }
40✔
213
            {
214
                STOPWATCH("print");
16✔
215
                std::fwrite(evts.data(), 1, evts.size(), stdout);
16✔
216
            }
16✔
217
            break;
16✔
218
        }
40✔
219
        case evts_type::testsuite_ints:
24✔
220
        {
221
            substr ssrc = to_substr(src);
12✔
222
            int estimated_size = estimate_ints_size(ssrc, args.ints_size);
48✔
223
            IntEvents int_evts((size_t)estimated_size);
24✔
224
            std::string ts_evts;
24✔
225
            {
226
                STOPWATCH("testsuite_ints");
24✔
227
                ts_evts = emit_testsuite_events_from_ints(args.filename, to_substr(src), int_evts, args.ints_size_force);
48✔
228
            }
24✔
229
            {
230
                STOPWATCH("print");
16✔
231
                std::fwrite(ts_evts.data(), 1, ts_evts.size(), stdout);
16✔
232
            }
16✔
233
            break;
16✔
234
        }
32✔
235
        case evts_type::ryml_ints:
8✔
236
        {
237
            substr ssrc = to_substr(src);
4✔
238
            int estimated_size = estimate_ints_size(ssrc, args.ints_size);
16✔
239
            IntEvents int_evts((size_t)estimated_size);
8✔
240
            {
241
                STOPWATCH("ryml_ints");
8✔
242
                emit_ints_events(args.filename, to_substr(src), int_evts, args.ints_size_force);
16✔
243
            }
8✔
244
            break;
8✔
245
        }
8✔
246
        }
247
    }
168✔
248
    C4_IF_EXCEPTIONS_(catch(std::exception const&), else)
64✔
249
    {
250
        return 1;
64✔
251
    }
64✔
252
    return 0;
48✔
253
}
254

255

256
//-----------------------------------------------------------------------------
257

258
std::string emit_testsuite_events_from_tree(csubstr filename, substr filecontents)
40✔
259
{
260
    Tree tree(create_custom_callbacks());
40✔
261
    {
262
        STOPWATCH("tree_reserve");
40✔
263
        tree.reserve(estimate_tree_capacity(filecontents));
40✔
264
    }
40✔
265
    {
266
        STOPWATCH("parse");
40✔
267
        parse_in_place(filename, filecontents, &tree);
40✔
268
    }
40✔
269
    {
270
        STOPWATCH("emit_events");
16✔
271
        std::string result = emit_events_from_tree<std::string>(tree);
16✔
272
        return result;
32✔
273
    }
16✔
274
}
40✔
275

276
extra::string emit_testsuite_events(csubstr filename, substr filecontents)
32✔
277
{
278
    extra::EventHandlerTestSuite::EventSink sink = {};
32✔
279
    extra::EventHandlerTestSuite handler(&sink, create_custom_callbacks());
32✔
280
    ParseEngine<extra::EventHandlerTestSuite> parser(&handler);
32✔
281
    {
282
        STOPWATCH("parse");
32✔
283
        parser.parse_in_place_ev(filename, filecontents);
32✔
284
    }
32✔
285
    return sink;
16✔
286
}
80✔
287

288
csubstr parse_events_ints(csubstr filename, substr filecontents, std::string &parsed, std::string &arena, IntEvents &evts, bool fail_size)
32✔
289
{
290
    using I = extra::ievt::DataType;
291
    using Handler = extra::EventHandlerInts;
292
    Handler handler(create_custom_callbacks());
32✔
293
    ParseEngine<Handler> parser(&handler);
32✔
294
    substr src = filecontents;
32✔
295
    if(!fail_size)
32✔
296
    {
297
        STOPWATCH("copy_src");
24✔
298
        parsed.assign(filecontents.str, filecontents.len);
24✔
299
        src = to_substr(parsed);
24✔
300
    }
24✔
301
    arena.resize(src.len);
32✔
302
    handler.reset(src, to_substr(arena), evts.data(), (I)evts.size());
64✔
303
    {
304
        STOPWATCH("parse");
32✔
305
        parser.parse_in_place_ev(filename, src);
32✔
306
    }
32✔
307
    size_t sz = (size_t)handler.required_size_events();
32✔
308
    if(timing_enabled) fprintf(stderr, "current_size=%zu vs needed_size=%zu. arena_size=%zu\n", evts.size(), sz, arena.size());
32✔
309
    if (!handler.fits_buffers())
32✔
310
    {
311
        RYML_CHECK(!fail_size);
16✔
312
        {
313
            STOPWATCH("resize");
8✔
314
            evts.resize(sz);
8✔
315
            arena.resize(handler.required_size_arena());
8✔
316
        }
8✔
317
        {
318
            STOPWATCH("redo_copy_src");
8✔
319
            parsed.assign(filecontents.str, filecontents.len);
8✔
320
            src = to_substr(parsed);
8✔
321
        }
8✔
322
        handler.reset(src, to_substr(arena), evts.data(), (I)evts.size());
16✔
323
        {
324
            STOPWATCH("redo_parse");
8✔
325
            parser.parse_in_place_ev(filename, src);
8✔
326
        }
8✔
327
        RYML_CHECK((size_t)handler.m_evt_pos == sz);
8✔
328
    }
329
    evts.resize(sz);
24✔
330
    return src;
48✔
331
}
40✔
332

333
std::string emit_testsuite_events_from_ints(csubstr filename, substr filecontents, IntEvents &evts, bool fail_size)
24✔
334
{
335
    using I = extra::ievt::DataType;
336
    std::string buf;
24✔
337
    std::string arena;
24✔
338
    csubstr parsed;
24✔
339
    {
340
        STOPWATCH("events");
24✔
341
        parsed = parse_events_ints(filename, filecontents, buf, arena, evts, fail_size);
24✔
342
    }
24✔
343
    std::string result;
16✔
344
    {
345
        STOPWATCH("emit");
16✔
346
        extra::emit_events_test_suite_from_ints(parsed, to_substr(arena), evts.data(), (I)evts.size(), &result);
40✔
347
    }
16✔
348
    return result;
32✔
349
}
32✔
350

351
void emit_ints_events(csubstr filename, substr filecontents, IntEvents &evts, bool fail_size)
8✔
352
{
353
    using I = extra::ievt::DataType;
354
    std::string buf;
8✔
355
    std::string arena;
8✔
356
    csubstr parsed;
8✔
357
    {
358
        STOPWATCH("events");
8✔
359
        parsed = parse_events_ints(filename, filecontents, buf, arena, evts, fail_size);
8✔
360
    }
8✔
361
    {
362
        STOPWATCH("emit");
8✔
363
        extra::print_events_ints(parsed, to_substr(arena), evts.data(), (I)evts.size());
20✔
364
    }
8✔
365
}
8✔
366

367
int estimate_ints_size(csubstr filecontents, int size)
32✔
368
{
369
    if(size < 0)
32✔
370
    {
371
        STOPWATCH("estimate_size");
16✔
372
        int est = extra::estimate_events_ints_size(filecontents);
16✔
373
        if(timing_enabled) fprintf(stderr, "estimated_size=%d*%d=%d\n", -size, est, -size * est);
16✔
374
        size = -size * est;
16✔
375
    }
16✔
376
    return size;
32✔
377
}
378

379

380
//-----------------------------------------------------------------------------
381

382
// return true if the program should continue (eg -h should exit)
383
bool Args::parse(Args *args, int argc, const char *argv[], int *errcode)
160✔
384
{
385
    *errcode = 0;
160✔
386
    auto argerr = [&](const char *msg){
32✔
387
        std::printf(usage);
32✔
388
        std::printf("ERROR: %s\n", msg);
32✔
389
        *errcode = 1;
32✔
390
        return false;
32✔
391
    };
160✔
392
    // set defaults
393
    *args = {};
160✔
394
    // parse the command argument
395
    if(argc < 2)
160✔
NEW
396
        return argerr("must provide a command");
×
397
    bool has_cmd = false;
160✔
398
    {
399
        csubstr s = to_csubstr(argv[1]);
240✔
400
        if(s == "testsuite_src" || s == "ts_src" || s == "tss")
480✔
401
        {
402
            args->evts = evts_type::testsuite_src;
32✔
403
            has_cmd = true;
32✔
404
        }
405
        else if(s == "testsuite_tree" || s == "ts_tree" || s == "tst")
384✔
406
        {
407
            args->evts = evts_type::testsuite_tree;
48✔
408
            has_cmd = true;
48✔
409
        }
410
        else if(s == "testsuite_ints" || s == "ts_ints" || s == "tsi")
240✔
411
        {
412
            args->evts = evts_type::testsuite_ints;
48✔
413
            has_cmd = true;
48✔
414
        }
415
        else if(s == "ryml_ints" || s == "ri")
64✔
416
        {
417
            args->evts = evts_type::ryml_ints;
8✔
418
            has_cmd = true;
8✔
419
        }
420
        else if(s == "--help" || s == "-h")
40✔
421
        {
422
            std::printf(usage);
8✔
423
            return false;
8✔
424
        }
425
    }
426
    if(!has_cmd)
152✔
427
        return argerr("unknown command");
16✔
428
    // parse other args
429
    int i = 2; // cmd is mandatory at i=1, so start after that at i=2
136✔
430
    for(; i < argc; ++i)
280✔
431
    {
432
        csubstr arg = to_csubstr(argv[i]);
252✔
433
        if(arg == "--help" || arg == "-h")
328✔
434
        {
435
            std::printf(usage);
8✔
436
            return false;
24✔
437
        }
438
        else if(arg == "--timings" || arg == "--timing" || arg == "-t")
472✔
439
        {
440
            timing_enabled = true;
8✔
441
        }
442
        else if(arg == "--ints-size-force" || arg == "-isf")
296✔
443
        {
444
            args->ints_size_force = true;
8✔
445
        }
446
        else if(arg == "--ints-size" || arg == "-is")
256✔
447
        {
448
            if(i + 1 >= argc)
32✔
NEW
449
                return argerr("ints-size value not given");
×
450
            else if(!atoi(to_csubstr(argv[i+1]), &args->ints_size))
64✔
451
                return argerr("ints-size value fails to parse");
16✔
452
            ++i; // shift 1
16✔
453
        }
454
        else
455
        {
456
            args->filename = arg;
112✔
457
        }
458
    }
459
    return true;
112✔
460
}
461

462
std::string load_file(csubstr filename)
112✔
463
{
464
    if(filename == "-") // read from stdin
112✔
465
    {
466
        std::string buf;
×
NEW
467
        buf.reserve(128);
×
UNCOV
468
        for(int c = std::getchar(); c != EOF; c = std::getchar())
×
UNCOV
469
            buf.push_back(static_cast<char>(c));
×
470
        return buf;
×
471
    }
×
472
    else if(!fs::path_exists(filename.str))
112✔
473
    {
474
        std::fprintf(stderr, "%s: file not found (cwd=%s)\n", filename.str, fs::cwd<std::string>().c_str());
8✔
475
        error("file not found");
8✔
476
    }
477
    return fs::file_get_contents<std::string>(filename.str);
104✔
478
}
479

480
void report_error(const char* msg, size_t length, Location loc, FILE *f)
64✔
481
{
482
    if(!loc.name.empty())
64✔
483
    {
484
        fwrite(loc.name.str, 1, loc.name.len, f);
56✔
485
        fputc(':', f);
56✔
486
    }
487
    fprintf(f, "%zu:", loc.line);
64✔
488
    if(loc.col)
64✔
489
        fprintf(f, "%zu:", loc.col);
48✔
490
    if(loc.offset)
64✔
491
        fprintf(f, " (%zuB):", loc.offset);
48✔
492
    fputc(' ', f);
64✔
493
    fprintf(f, "%.*s\n", static_cast<int>(length), msg);
64✔
494
    fflush(f);
64✔
495
}
64✔
496

497
Callbacks create_custom_callbacks()
216✔
498
{
499
    Callbacks callbacks = {};
216✔
500
    callbacks.m_error = [](const char *msg, size_t msg_len, Location location, void *)
216✔
501
    {
502
        report_error(msg, msg_len, location, stderr);
64✔
503
        C4_IF_EXCEPTIONS(
192✔
504
            throw std::runtime_error({msg, msg_len});
505
            ,
506
            jmp_msg.assign(msg, msg_len);
507
            std::longjmp(jmp_env, 1);
508
        );
509
    };
510
    return callbacks;
216✔
511
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc