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

mbits-os / quick_dra / 22041537858

15 Feb 2026 07:18PM UTC coverage: 95.231% (+8.2%) from 87.008%
22041537858

Pull #39

github

web-flow
Merge 78efb5eb1 into 7d773e4d9
Pull Request #39: testing

16 of 18 new or added lines in 8 files covered. (88.89%)

35 existing lines in 6 files now uncovered.

4193 of 4403 relevant lines covered (95.23%)

1610.05 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()(
2,880✔
15
                            std::string const& field) const noexcept {
1✔
16
                                return compile_field(field);
8,641✔
17
                        }
5,761✔
18
                        maybe_list<compiled_value> operator()(
90✔
19
                            std::vector<std::string> const& fields) const noexcept {
1✔
20
                                std::vector<compiled_value> result{};
271✔
21
                                result.reserve(fields.size());
271✔
22
                                std::transform(fields.begin(), fields.end(),
271✔
23
                                               std::back_inserter(result), compile_field);
180✔
24
                                return result;
271✔
25
                        }
271✔
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) {
361✔
34
                        auto const parts = split_sv(input.substr(2), ','_sep);
1,081✔
35
                        std::vector<unsigned> refs{};
1,081✔
36
                        refs.reserve(parts.size());
1,081✔
37

720✔
38
                        for (auto const& part : parts) {
3,871✔
39
                                auto const trimmed = strip_sv(part);
3,511✔
40
                                unsigned ref{};
3,511✔
41

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

2,340✔
50
                                refs.push_back(ref);
1,171✔
51
                        }
2,341✔
52
                        return addition{.refs = std::move(refs)};
361✔
53
                }
1,081✔
54

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

2,160✔
64
                        return value;
1,081✔
65
                }
2,161✔
66

67
                compiled_value compile_field(std::string_view input) {
3,061✔
68
                        if (input.starts_with('$')) {
3,061✔
69
                                if (input.starts_with("$+"sv)) {
1,951✔
70
                                        return compile_sum(input);
1,081✔
71
                                }
720✔
72

3,180✔
73
                                return varname::parse(input);
1,591✔
74
                        }
3,900✔
75

2,220✔
76
                        if (input.ends_with("zł")) {
1,111✔
77
                                return compile_currency(input);
3,241✔
78
                        }
2,160✔
79

60✔
80
                        return std::string{input.data(), input.size()};
91✔
81
                }
2,221✔
82

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

1,020✔
86
                        if (input.block) {
511✔
87
                                result.id = *input.block;
361✔
88
                        }
240✔
89

1,020✔
90
                        for (auto const& [index, field] : input.fields) {
9,421✔
91
                                result.fields[index] = std::visit(field_compiler{}, field);
8,911✔
92
                        }
5,941✔
93

1,020✔
94
                        return result;
1,531✔
95
                }
1,021✔
96

97
                std::vector<compiled_section> compile_report(
60✔
98
                    std::vector<report_section> const& input) {
1✔
99
                        std::map<std::string, section_stats> stats{};
181✔
100
                        for (auto const& section : input) {
1,681✔
101
                                auto it = stats.lower_bound(section.id);
1,621✔
102
                                if (it == stats.end() || it->first != section.id) {
541✔
103
                                        it = stats.insert(it, {section.id, section_stats{}});
1,261✔
104
                                }
840✔
105

1,080✔
106
                                auto& stat = it->second;
1,621✔
107

1,080✔
108
                                ++stat.block_count;
1,621✔
109
                                stat.repeatable |= section.repeatable.value_or(false);
1,621✔
110
                        }
1,081✔
111
                        std::vector<compiled_section> result{};
181✔
112
                        result.reserve(stats.size());
181✔
113
                        for (auto const& section : input) {
1,681✔
114
                                if (section.fields.empty() && !section.block) continue;
541✔
115

1,020✔
116
                                auto it = std::find_if(
681✔
117
                                    result.begin(), result.end(),
1,020✔
118
                                    [&id = section.id](compiled_section const& out) {
511✔
119
                                            return out.id == id;
5,401✔
120
                                    });
3,601✔
121

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

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

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

840✔
134
                                        if (block_count) {
421✔
135
                                                result.back().blocks.reserve(block_count);
1,261✔
136
                                        }
840✔
137

840✔
138
                                        it = std::prev(result.end());
1,681✔
139
                                }
841✔
140

1,020✔
141
                                auto& output = *it;
1,531✔
142
                                output.blocks.push_back(compile_block(section));
1,531✔
143
                        }
1,021✔
144
                        return result;
201✔
145
                }
601✔
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(
510✔
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,531✔
158
                                return src.calculate();
2,041✔
159
                        }
1,531✔
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 {
4,771✔
168
                                        return value;
14,311✔
169
                                }
9,541✔
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,231✔
174
                                        return std::visit(*this, field);
12,691✔
175
                                }
8,461✔
176

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

188
                        template <typename To, typename From>
189
                        maybe_list<To> extract(maybe_list<From> const& val) {
4,381✔
190
                                return std::visit(value_extractor<From, To>{}, val);
13,141✔
191
                        }
8,761✔
192

193
                        template <typename To, typename From>
194
                        To extract(From const& val) {
181✔
195
                                return std::visit(value_extractor<From, To>{}, val);
541✔
196
                        }
361✔
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,590✔
207
                                      size_t index,
208
                                      compiled_value& tgt,
209
                                      varname const& var) {
1✔
210
                                auto const ptr = ctx.peek(var);
4,771✔
211
                                if (!ptr) {
1,591✔
UNCOV
212
                                        fmt::print(stderr, "{}: error: cannot find `${}'\n",
×
213
                                                   label(key, index), join(var.path, '.'_sep));
UNCOV
214
                                        return;
×
215
                                }
216

3,180✔
217
                                auto const& data = *ptr;
1,591✔
218

3,180✔
219
                                if (!data.value) {
1,591✔
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,180✔
227
                                auto const& src = *data.value;
1,591✔
228
                                if (index == invalid_index) {
1,591✔
229
                                        fields.find(key)->second = extract<compiled_value>(src);
4,231✔
230
                                        return;
4,231✔
231
                                }
2,820✔
232

360✔
233
                                if (std::holds_alternative<calculated_value>(src)) {
181✔
234
                                        tgt = extract<compiled_value>(
721✔
235
                                            std::get<calculated_value>(src));
540✔
236
                                        return;
541✔
237
                                }
360✔
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,061✔
246
                                if (std::holds_alternative<varname>(tgt)) {
3,061✔
247
                                        fill_var(key, index, tgt, std::get<varname>(tgt));
4,771✔
248
                                        return;
4,771✔
249
                                }
3,180✔
250

2,940✔
251
                                if (std::holds_alternative<addition>(tgt)) {
1,471✔
252
                                        auto const& refs = std::get<addition>(tgt).refs;
1,081✔
253

720✔
254
                                        if (index != invalid_index) {
361✔
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

720✔
262
                                        crossrefs[key] =
361✔
263
                                            std::set<unsigned>{refs.begin(), refs.end()};
1,440✔
264
                                }
720✔
265
                        }
2,941✔
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() {
601✔
274
                                std::set<unsigned> result{};
1,801✔
275

1,200✔
276
                                for (auto& [key, refs] : crossrefs) {
2,251✔
277
                                        auto copy = refs;
1,351✔
278
                                        for (auto ref : copy) {
4,681✔
279
                                                auto it = crossrefs.find(ref);
4,231✔
280
                                                if (it == crossrefs.end()) {
1,411✔
281
                                                        refs.erase(ref);
3,511✔
282
                                                }
2,340✔
283
                                        }
2,821✔
284

900✔
285
                                        if (refs.empty()) {
451✔
286
                                                result.insert(key);
1,081✔
287
                                        }
720✔
288
                                }
1,351✔
289

1,200✔
290
                                for (auto key : result) {
2,161✔
291
                                        crossrefs.erase(key);
1,081✔
292
                                }
721✔
293

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

298
                                return result;
1,801✔
299
                        }
1,201✔
300

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

720✔
305
                                currency result{};
1,081✔
306

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

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

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

2,340✔
329
                                        result = result + *val;
1,171✔
330
                                }
2,341✔
331

720✔
332
                                tgt = result;
361✔
333
                        }
721✔
334

335
                        void remove_crossrefs() {
511✔
336
                                // print_crossrefs();
1,020✔
337
                                auto layer = get_crossrefs_layer();
1,531✔
338
                                while (!layer.empty()) {
601✔
339
                                        for (auto key : layer) {
1,171✔
340
                                                calculate_sum(key);
1,081✔
341
                                        }
721✔
342
                                        layer = get_crossrefs_layer();
271✔
343
                                }
181✔
344
                        }
1,701✔
345

346
                        mapped_value<calculated_value> calculate() {
511✔
347
                                for (auto& [key, field] : fields) {
9,421✔
348
                                        if (std::holds_alternative<compiled_value>(field)) {
2,971✔
349
                                                precalc(key, invalid_index,
9,601✔
350
                                                        std::get<compiled_value>(field));
8,640✔
351
                                        } else {
5,761✔
352
                                                size_t index = 0;
271✔
353
                                                for (auto& child :
271✔
354
                                                     std::get<std::vector<compiled_value>>(field)) {
721✔
355
                                                        precalc(key, index, child);
541✔
356
                                                        ++index;
541✔
357
                                                }
361✔
358
                                        }
180✔
359
                                }
5,941✔
360

1,020✔
361
                                remove_crossrefs();
1,531✔
362

1,020✔
363
                                mapped_value<calculated_value> result{};
1,531✔
364
                                for (auto const& [key, field] : fields) {
9,421✔
365
                                        result[key] = extract<calculated_value>(field);
8,911✔
366
                                }
5,941✔
367
                                return result;
1,531✔
368
                        }
1,021✔
369
                };
370
        }  // namespace
371

372
        calculated_block calculate(compiled_block const& self,
510✔
373
                                   global_object const& ctx,
374
                                   std::string_view log_name) {
1✔
375
                std::string extended_name;
1,531✔
376
                if (!self.id.empty()) {
511✔
377
                        extended_name = fmt::format("{}.{}", log_name, self.id);
481✔
378
                        log_name = extended_name;
361✔
379
                }
240✔
380
                return calculated_block{
1,361✔
381
                    .id = self.id,
1,530✔
382
                    .fields = data_calculator::calculate(log_name, self.fields, ctx)};
2,210✔
383
        }
1,531✔
384

385
        calculated_section calculate(compiled_section const& self,
420✔
386
                                     global_object const& ctx) {
1✔
387
                calculated_section result{.id = self.id, .repeatable = self.repeatable};
1,261✔
388
                result.blocks.reserve(self.blocks.size());
1,261✔
389
                for (auto const& block : self.blocks) {
1,951✔
390
                        result.blocks.push_back(calculate(block, ctx, self.id));
1,531✔
391
                }
1,021✔
392
                return result;
1,261✔
393
        }
841✔
394

395
        std::vector<calculated_section> calculate(
60✔
396
            std::vector<compiled_section> const& report,
397
            global_object const& ctx) {
1✔
398
                std::vector<calculated_section> result{};
181✔
399
                result.reserve(report.size());
181✔
400
                for (auto const& section : report) {
1,321✔
401
                        result.push_back(calculate(section, ctx));
1,261✔
402
                }
841✔
403
                return result;
181✔
404
        }
121✔
405

406
        compiled_templates compiled_templates::compile(templates const& input) {
31✔
407
                compiled_templates result{};
91✔
408
                for (auto const& [key, report] : input.reports) {
211✔
409
                        result.reports[key] = compile_report(report);
181✔
410
                }
121✔
411
                return result;
91✔
412
        }
61✔
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