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

mbits-os / quick_dra / 22073125303

16 Feb 2026 06:03PM UTC coverage: 97.984% (+11.0%) from 87.008%
22073125303

Pull #39

github

web-flow
Merge 5963f011b into 7d773e4d9
Pull Request #39: testing

60 of 60 new or added lines in 18 files covered. (100.0%)

12 existing lines in 1 file now uncovered.

4422 of 4513 relevant lines covered (97.98%)

1926.23 hits per line

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

94.79
/libs/libmodels/src/models/compiler.cpp
1
// Copyright (c) 2026 midnightBITS
2
// This code is licensed under MIT license (see LICENSE for details)
3

4
#include <map>
5
#include <quick_dra/base/str.hpp>
6
#include <quick_dra/models/types.hpp>
7
#include <set>
8

9
namespace quick_dra {
10
        namespace {
11
                compiled_value compile_field(std::string_view input);
12

13
                struct field_compiler {
14
                        maybe_list<compiled_value> operator()(
3,168✔
15
                            std::string const& field) const noexcept {
1✔
16
                                return compile_field(field);
9,505✔
17
                        }
6,337✔
18
                        maybe_list<compiled_value> operator()(
99✔
19
                            std::vector<std::string> const& fields) const noexcept {
1✔
20
                                std::vector<compiled_value> result{};
298✔
21
                                result.reserve(fields.size());
298✔
22
                                std::transform(fields.begin(), fields.end(),
298✔
23
                                               std::back_inserter(result), compile_field);
198✔
24
                                return result;
298✔
25
                        }
298✔
26
                };
27

28
                struct section_stats {
29
                        size_t block_count{0};
30
                        bool repeatable{false};
31
                };
32

33
                compiled_value compile_sum(std::string_view input) {
397✔
34
                        auto const parts = split_sv(input.substr(2), ','_sep);
1,189✔
35
                        std::vector<unsigned> refs{};
1,189✔
36
                        refs.reserve(parts.size());
1,189✔
37

792✔
38
                        for (auto const& part : parts) {
4,258✔
39
                                auto const trimmed = strip_sv(part);
3,862✔
40
                                unsigned ref{};
3,862✔
41

2,574✔
42
                                auto const begin = trimmed.data();
3,862✔
43
                                auto const end = begin + trimmed.size();
3,862✔
44
                                auto const [ptr, ec] = std::from_chars(begin, end, ref);
3,862✔
45
                                if (ptr != end || ec != std::errc{}) {
1,288✔
46
                                        fmt::print(stderr, "error while parsing `{}'\n", input);
×
47
                                        return fmt::format("unparsable: {}", input);
×
48
                                }
49

2,574✔
50
                                refs.push_back(ref);
1,288✔
51
                        }
2,575✔
52
                        return addition{.refs = std::move(refs)};
397✔
53
                }
1,189✔
54

55
                compiled_value compile_currency(std::string_view input) {
1,189✔
56
                        auto const view =
2,772✔
57
                            strip_sv(input.substr(0, input.size() - "zł"sv.size()));
3,565✔
58
                        currency value{};
3,565✔
59
                        if (!currency::parse(view, value)) {
1,189✔
60
                                fmt::print(stderr, "error while parsing `{}'\n", input);
×
61
                                return fmt::format("unparsable: {}", input);
×
62
                        }
63

2,376✔
64
                        return value;
1,189✔
65
                }
2,377✔
66

67
                compiled_value compile_field(std::string_view input) {
3,367✔
68
                        if (input.starts_with('$')) {
3,367✔
69
                                if (input.starts_with("$+"sv)) {
2,146✔
70
                                        return compile_sum(input);
1,189✔
71
                                }
792✔
72

3,498✔
73
                                return varname::parse(input);
1,750✔
74
                        }
4,290✔
75

2,442✔
76
                        if (input.ends_with("zł")) {
1,222✔
77
                                return compile_currency(input);
3,565✔
78
                        }
2,376✔
79

66✔
80
                        return std::string{input.data(), input.size()};
100✔
81
                }
2,443✔
82

83
                compiled_block compile_block(report_section const& input) {
562✔
84
                        compiled_block result{};
1,683✔
85

1,122✔
86
                        if (input.block) {
562✔
87
                                result.id = *input.block;
397✔
88
                        }
264✔
89

1,122✔
90
                        for (auto const& [index, field] : input.fields) {
10,363✔
91
                                result.fields[index] = std::visit(field_compiler{}, field);
9,802✔
92
                        }
6,535✔
93

1,122✔
94
                        return result;
1,684✔
95
                }
1,123✔
96

97
                std::vector<compiled_section> compile_report(
66✔
98
                    std::vector<report_section> const& input) {
1✔
99
                        std::map<std::string, section_stats> stats{};
199✔
100
                        for (auto const& section : input) {
1,849✔
101
                                auto it = stats.lower_bound(section.id);
1,783✔
102
                                if (it == stats.end() || it->first != section.id) {
595✔
103
                                        it = stats.insert(it, {section.id, section_stats{}});
1,387✔
104
                                }
924✔
105

1,188✔
106
                                auto& stat = it->second;
1,783✔
107

1,188✔
108
                                ++stat.block_count;
1,783✔
109
                                stat.repeatable |= section.repeatable.value_or(false);
1,783✔
110
                        }
1,189✔
111
                        std::vector<compiled_section> result{};
199✔
112
                        result.reserve(stats.size());
199✔
113
                        for (auto const& section : input) {
1,849✔
114
                                if (section.fields.empty() && !section.block) continue;
595✔
115

1,122✔
116
                                auto it = std::find_if(
749✔
117
                                    result.begin(), result.end(),
1,122✔
118
                                    [&id = section.id](compiled_section const& out) {
562✔
119
                                            return out.id == id;
5,941✔
120
                                    });
3,961✔
121

1,122✔
122
                                if (it == result.end()) {
562✔
123
                                        size_t block_count = 0;
1,387✔
124
                                        bool repeatable = false;
1,387✔
125
                                        auto stat_it = stats.find(section.id);
1,387✔
126

924✔
127
                                        if (stat_it != stats.end()) {
463✔
128
                                                block_count = stat_it->second.block_count;
1,387✔
129
                                                repeatable = stat_it->second.repeatable;
1,387✔
130
                                        }
924✔
131

924✔
132
                                        result.push_back({section.id, repeatable});
1,541✔
133

924✔
134
                                        if (block_count) {
463✔
135
                                                result.back().blocks.reserve(block_count);
1,387✔
136
                                        }
924✔
137

924✔
138
                                        it = std::prev(result.end());
1,849✔
139
                                }
925✔
140

1,122✔
141
                                auto& output = *it;
1,684✔
142
                                output.blocks.push_back(compile_block(section));
1,684✔
143
                        }
1,123✔
144
                        return result;
221✔
145
                }
661✔
146

147
                struct data_calculator {
148
                        std::string_view id;
149
                        global_object const& ctx;
150
                        mapped_value<compiled_value> fields;
151
                        std::map<unsigned, std::set<unsigned>> crossrefs{};
1✔
152

153
                        static mapped_value<calculated_value> calculate(
561✔
154
                            std::string_view id,
155
                            mapped_value<compiled_value> const& fields,
156
                            global_object const& ctx) {
1✔
157
                                data_calculator src{id, ctx, fields};
1,684✔
158
                                return src.calculate();
2,245✔
159
                        }
1,684✔
160

161
                private:
162
                        static constexpr auto invalid_index =
163
                            std::numeric_limits<size_t>::max();
164

165
                        template <typename From, typename To>
166
                        struct value_extractor {
167
                                To operator()(auto const& value) const noexcept {
5,248✔
168
                                        return value;
15,742✔
169
                                }
10,495✔
170
                                To operator()(addition const&) const noexcept { return {}; }
171
                                To operator()(varname const&) const noexcept { return {}; }
172

173
                                maybe_list<To> operator()(From const& field) const noexcept {
4,654✔
174
                                        return std::visit(*this, field);
13,960✔
175
                                }
9,307✔
176

177
                                maybe_list<To> operator()(
165✔
178
                                    std::vector<From> const& fields) const noexcept {
1✔
179
                                        std::vector<To> result{};
496✔
180
                                        result.reserve(fields.size());
496✔
181
                                        for (auto const& field : fields) {
1,354✔
182
                                                result.push_back(std::visit(*this, field));
1,189✔
183
                                        }
793✔
184
                                        return result;
496✔
185
                                }
496✔
186
                        };
187

188
                        template <typename To, typename From>
189
                        maybe_list<To> extract(maybe_list<From> const& val) {
4,819✔
190
                                return std::visit(value_extractor<From, To>{}, val);
14,455✔
191
                        }
9,637✔
192

193
                        template <typename To, typename From>
194
                        To extract(From const& val) {
199✔
195
                                return std::visit(value_extractor<From, To>{}, val);
595✔
196
                        }
397✔
197

198
                        inline std::string label(unsigned key,
199
                                                 size_t index = invalid_index) {
200
                                return fmt::format(
201
                                    fmt::runtime(index == invalid_index ? "{} p{}"
202
                                                                        : "{} p{}.{}"),
203
                                    id, key, index);
204
                        }
205

206
                        void fill_var(unsigned key,
1,749✔
207
                                      size_t index,
208
                                      compiled_value& tgt,
209
                                      varname const& var) {
1✔
210
                                auto const ptr = ctx.peek(var);
5,248✔
211
                                if (!ptr) {
1,750✔
UNCOV
212
                                        fmt::print(stderr, "{}: error: cannot find `${}'\n",
×
213
                                                   label(key, index), join(var.path, '.'_sep));
UNCOV
214
                                        return;
×
215
                                }
216

3,498✔
217
                                auto const& data = *ptr;
1,750✔
218

3,498✔
219
                                if (!data.value) {
1,750✔
UNCOV
220
                                        fmt::print(stderr,
×
221
                                                   "{}: error: reference `${}' contains no "
222
                                                   "value\n",
223
                                                   label(key, index), join(var.path, '.'_sep));
UNCOV
224
                                        return;
×
225
                                }
226

3,498✔
227
                                auto const& src = *data.value;
1,750✔
228
                                if (index == invalid_index) {
1,750✔
229
                                        fields.find(key)->second = extract<compiled_value>(src);
4,654✔
230
                                        return;
4,654✔
231
                                }
3,102✔
232

396✔
233
                                if (std::holds_alternative<calculated_value>(src)) {
199✔
234
                                        tgt = extract<compiled_value>(
793✔
235
                                            std::get<calculated_value>(src));
594✔
236
                                        return;
595✔
237
                                }
396✔
238

239
                                fmt::print(stderr,
240
                                           "{}: error: cannot assign a list to a "
241
                                           "list item when checking `${}'\n",
242
                                           label(key, index), join(var.path, '.'_sep));
243
                        }
1✔
244

245
                        void precalc(unsigned key, size_t index, compiled_value& tgt) {
3,367✔
246
                                if (std::holds_alternative<varname>(tgt)) {
3,367✔
247
                                        fill_var(key, index, tgt, std::get<varname>(tgt));
5,248✔
248
                                        return;
5,248✔
249
                                }
3,498✔
250

3,234✔
251
                                if (std::holds_alternative<addition>(tgt)) {
1,618✔
252
                                        auto const& refs = std::get<addition>(tgt).refs;
1,189✔
253

792✔
254
                                        if (index != invalid_index) {
397✔
UNCOV
255
                                                fmt::print(stderr,
×
256
                                                           "{}: error: field addition inside a "
257
                                                           "sub-field ({})\n",
258
                                                           label(key, index), fmt::join(refs, " + "));
UNCOV
259
                                                return;
×
260
                                        }
261

792✔
262
                                        crossrefs[key] =
397✔
263
                                            std::set<unsigned>{refs.begin(), refs.end()};
1,584✔
264
                                }
792✔
265
                        }
3,235✔
266

267
                        void print_crossrefs() {
268
                                for (auto const& pair : crossrefs) {
269
                                        fmt::print("{} {}\n", id, pair);
270
                                }
271
                        }
272

273
                        std::set<unsigned> get_crossrefs_layer() {
661✔
274
                                std::set<unsigned> result{};
1,981✔
275

1,320✔
276
                                for (auto& [key, refs] : crossrefs) {
2,476✔
277
                                        auto copy = refs;
1,486✔
278
                                        for (auto ref : copy) {
5,149✔
279
                                                auto it = crossrefs.find(ref);
4,654✔
280
                                                if (it == crossrefs.end()) {
1,552✔
281
                                                        refs.erase(ref);
3,862✔
282
                                                }
2,574✔
283
                                        }
3,103✔
284

990✔
285
                                        if (refs.empty()) {
496✔
286
                                                result.insert(key);
1,189✔
287
                                        }
792✔
288
                                }
1,486✔
289

1,320✔
290
                                for (auto key : result) {
2,377✔
291
                                        crossrefs.erase(key);
1,189✔
292
                                }
793✔
293

1,320✔
294
                                // fmt::print("\n");
295
                                // print_crossrefs();
296
                                // fmt::print(" -- {}\n", result);
297

298
                                return result;
1,981✔
299
                        }
1,321✔
300

301
                        void calculate_sum(unsigned key) {
397✔
302
                                auto& tgt = std::get<compiled_value>(fields.find(key)->second);
1,189✔
303
                                auto& refs = std::get<addition>(tgt).refs;
1,189✔
304

792✔
305
                                currency result{};
1,189✔
306

792✔
307
                                for (auto ref : refs) {
4,258✔
308
                                        auto it = fields.find(ref);
3,862✔
309
                                        if (it == fields.end()) {
1,288✔
UNCOV
310
                                                fmt::print(stderr, "{}: error: cannot find p{}\n",
×
311
                                                           label(key), ref);
UNCOV
312
                                                return;
×
313
                                        }
314

2,574✔
315
                                        auto src = std::get_if<compiled_value>(&it->second);
1,288✔
316
                                        if (!src) {
1,288✔
UNCOV
317
                                                fmt::print(stderr, "{}: error: p{} is not a scalar\n",
×
318
                                                           label(key), ref);
UNCOV
319
                                                return;
×
320
                                        }
321

2,574✔
322
                                        auto val = std::get_if<currency>(src);
1,288✔
323
                                        if (!val) {
1,288✔
UNCOV
324
                                                fmt::print(stderr, "{}: error: p{} is not a number\n",
×
325
                                                           label(key), ref);
UNCOV
326
                                                return;
×
327
                                        }
328

2,574✔
329
                                        result = result + *val;
1,288✔
330
                                }
2,575✔
331

792✔
332
                                tgt = result;
397✔
333
                        }
793✔
334

335
                        void remove_crossrefs() {
562✔
336
                                // print_crossrefs();
1,122✔
337
                                auto layer = get_crossrefs_layer();
1,684✔
338
                                while (!layer.empty()) {
661✔
339
                                        for (auto key : layer) {
1,288✔
340
                                                calculate_sum(key);
1,189✔
341
                                        }
793✔
342
                                        layer = get_crossrefs_layer();
298✔
343
                                }
199✔
344
                        }
1,871✔
345

346
                        mapped_value<calculated_value> calculate() {
562✔
347
                                for (auto& [key, field] : fields) {
10,363✔
348
                                        if (std::holds_alternative<compiled_value>(field)) {
3,268✔
349
                                                precalc(key, invalid_index,
10,561✔
350
                                                        std::get<compiled_value>(field));
9,504✔
351
                                        } else {
6,337✔
352
                                                size_t index = 0;
298✔
353
                                                for (auto& child :
298✔
354
                                                     std::get<std::vector<compiled_value>>(field)) {
793✔
355
                                                        precalc(key, index, child);
595✔
356
                                                        ++index;
595✔
357
                                                }
397✔
358
                                        }
198✔
359
                                }
6,535✔
360

1,122✔
361
                                remove_crossrefs();
1,684✔
362

1,122✔
363
                                mapped_value<calculated_value> result{};
1,684✔
364
                                for (auto const& [key, field] : fields) {
10,363✔
365
                                        result[key] = extract<calculated_value>(field);
9,802✔
366
                                }
6,535✔
367
                                return result;
1,684✔
368
                        }
1,123✔
369
                };
370
        }  // namespace
371

372
        calculated_block calculate(compiled_block const& self,
561✔
373
                                   global_object const& ctx,
374
                                   std::string_view log_name) {
1✔
375
                std::string extended_name;
1,684✔
376
                if (!self.id.empty()) {
562✔
377
                        extended_name = fmt::format("{}.{}", log_name, self.id);
529✔
378
                        log_name = extended_name;
397✔
379
                }
264✔
380
                return calculated_block{
1,497✔
381
                    .id = self.id,
1,683✔
382
                    .fields = data_calculator::calculate(log_name, self.fields, ctx)};
2,431✔
383
        }
1,684✔
384

385
        calculated_section calculate(compiled_section const& self,
462✔
386
                                     global_object const& ctx) {
1✔
387
                calculated_section result{.id = self.id, .repeatable = self.repeatable};
1,387✔
388
                result.blocks.reserve(self.blocks.size());
1,387✔
389
                for (auto const& block : self.blocks) {
2,146✔
390
                        result.blocks.push_back(calculate(block, ctx, self.id));
1,684✔
391
                }
1,123✔
392
                return result;
1,387✔
393
        }
925✔
394

395
        std::vector<calculated_section> calculate(
66✔
396
            std::vector<compiled_section> const& report,
397
            global_object const& ctx) {
1✔
398
                std::vector<calculated_section> result{};
199✔
399
                result.reserve(report.size());
199✔
400
                for (auto const& section : report) {
1,453✔
401
                        result.push_back(calculate(section, ctx));
1,387✔
402
                }
925✔
403
                return result;
199✔
404
        }
133✔
405

406
        compiled_templates compiled_templates::compile(templates const& input) {
34✔
407
                compiled_templates result{};
100✔
408
                for (auto const& [key, report] : input.reports) {
232✔
409
                        result.reports[key] = compile_report(report);
199✔
410
                }
133✔
411
                return result;
100✔
412
        }
67✔
413
}  // namespace quick_dra
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc