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

mbits-os / quick_dra / 22038263728

15 Feb 2026 03:31PM UTC coverage: 93.378% (+6.4%) from 87.008%
22038263728

Pull #39

github

web-flow
Merge 74a89bbd4 into 7d773e4d9
Pull Request #39: testing

58 of 67 new or added lines in 23 files covered. (86.57%)

75 existing lines in 12 files now uncovered.

4033 of 4319 relevant lines covered (93.38%)

1107.01 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,016✔
15
                            std::string const& field) const noexcept {
1✔
16
                                return compile_field(field);
6,049✔
17
                        }
4,033✔
18
                        maybe_list<compiled_value> operator()(
63✔
19
                            std::vector<std::string> const& fields) const noexcept {
1✔
20
                                std::vector<compiled_value> result{};
190✔
21
                                result.reserve(fields.size());
190✔
22
                                std::transform(fields.begin(), fields.end(),
190✔
23
                                               std::back_inserter(result), compile_field);
126✔
24
                                return result;
190✔
25
                        }
190✔
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) {
253✔
34
                        auto const parts = split_sv(input.substr(2), ','_sep);
757✔
35
                        std::vector<unsigned> refs{};
757✔
36
                        refs.reserve(parts.size());
757✔
37

504✔
38
                        for (auto const& part : parts) {
2,710✔
39
                                auto const trimmed = strip_sv(part);
2,458✔
40
                                unsigned ref{};
2,458✔
41

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

1,638✔
50
                                refs.push_back(ref);
820✔
51
                        }
1,639✔
52
                        return addition{.refs = std::move(refs)};
253✔
53
                }
757✔
54

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

1,512✔
64
                        return value;
757✔
65
                }
1,513✔
66

67
                compiled_value compile_field(std::string_view input) {
2,143✔
68
                        if (input.starts_with('$')) {
2,143✔
69
                                if (input.starts_with("$+"sv)) {
1,366✔
70
                                        return compile_sum(input);
757✔
71
                                }
504✔
72

2,226✔
73
                                return varname::parse(input);
1,114✔
74
                        }
2,730✔
75

1,554✔
76
                        if (input.ends_with("zł")) {
778✔
77
                                return compile_currency(input);
2,269✔
78
                        }
1,512✔
79

42✔
80
                        return std::string{input.data(), input.size()};
64✔
81
                }
1,555✔
82

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

714✔
86
                        if (input.block) {
358✔
87
                                result.id = *input.block;
253✔
88
                        }
168✔
89

714✔
90
                        for (auto const& [index, field] : input.fields) {
6,595✔
91
                                result.fields[index] = std::visit(field_compiler{}, field);
6,238✔
92
                        }
4,159✔
93

714✔
94
                        return result;
1,072✔
95
                }
715✔
96

97
                std::vector<compiled_section> compile_report(
42✔
98
                    std::vector<report_section> const& input) {
1✔
99
                        std::map<std::string, section_stats> stats{};
127✔
100
                        for (auto const& section : input) {
1,177✔
101
                                auto it = stats.lower_bound(section.id);
1,135✔
102
                                if (it == stats.end() || it->first != section.id) {
379✔
103
                                        it = stats.insert(it, {section.id, section_stats{}});
883✔
104
                                }
588✔
105

756✔
106
                                auto& stat = it->second;
1,135✔
107

756✔
108
                                ++stat.block_count;
1,135✔
109
                                stat.repeatable |= section.repeatable.value_or(false);
1,135✔
110
                        }
757✔
111
                        std::vector<compiled_section> result{};
127✔
112
                        result.reserve(stats.size());
127✔
113
                        for (auto const& section : input) {
1,177✔
114
                                if (section.fields.empty() && !section.block) continue;
379✔
115

714✔
116
                                auto it = std::find_if(
477✔
117
                                    result.begin(), result.end(),
714✔
118
                                    [&id = section.id](compiled_section const& out) {
358✔
119
                                            return out.id == id;
3,781✔
120
                                    });
2,521✔
121

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

588✔
127
                                        if (stat_it != stats.end()) {
295✔
128
                                                block_count = stat_it->second.block_count;
883✔
129
                                                repeatable = stat_it->second.repeatable;
883✔
130
                                        }
588✔
131

588✔
132
                                        result.push_back({section.id, repeatable});
981✔
133

588✔
134
                                        if (block_count) {
295✔
135
                                                result.back().blocks.reserve(block_count);
883✔
136
                                        }
588✔
137

588✔
138
                                        it = std::prev(result.end());
1,177✔
139
                                }
589✔
140

714✔
141
                                auto& output = *it;
1,072✔
142
                                output.blocks.push_back(compile_block(section));
1,072✔
143
                        }
715✔
144
                        return result;
141✔
145
                }
421✔
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(
357✔
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,072✔
158
                                return src.calculate();
1,429✔
159
                        }
1,072✔
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 {
3,340✔
168
                                        return value;
10,018✔
169
                                }
6,679✔
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 {
2,962✔
174
                                        return std::visit(*this, field);
8,884✔
175
                                }
5,923✔
176

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

188
                        template <typename To, typename From>
189
                        maybe_list<To> extract(maybe_list<From> const& val) {
3,067✔
190
                                return std::visit(value_extractor<From, To>{}, val);
9,199✔
191
                        }
6,133✔
192

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

2,226✔
217
                                auto const& data = *ptr;
1,114✔
218

2,226✔
219
                                if (!data.value) {
1,114✔
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

2,226✔
227
                                auto const& src = *data.value;
1,114✔
228
                                if (index == invalid_index) {
1,114✔
229
                                        fields.find(key)->second = extract<compiled_value>(src);
2,962✔
230
                                        return;
2,962✔
231
                                }
1,974✔
232

252✔
233
                                if (std::holds_alternative<calculated_value>(src)) {
127✔
234
                                        tgt = extract<compiled_value>(
505✔
235
                                            std::get<calculated_value>(src));
378✔
236
                                        return;
379✔
237
                                }
252✔
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) {
2,143✔
246
                                if (std::holds_alternative<varname>(tgt)) {
2,143✔
247
                                        fill_var(key, index, tgt, std::get<varname>(tgt));
3,340✔
248
                                        return;
3,340✔
249
                                }
2,226✔
250

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

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

504✔
262
                                        crossrefs[key] =
253✔
263
                                            std::set<unsigned>{refs.begin(), refs.end()};
1,008✔
264
                                }
504✔
265
                        }
2,059✔
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() {
421✔
274
                                std::set<unsigned> result{};
1,261✔
275

840✔
276
                                for (auto& [key, refs] : crossrefs) {
1,576✔
277
                                        auto copy = refs;
946✔
278
                                        for (auto ref : copy) {
3,277✔
279
                                                auto it = crossrefs.find(ref);
2,962✔
280
                                                if (it == crossrefs.end()) {
988✔
281
                                                        refs.erase(ref);
2,458✔
282
                                                }
1,638✔
283
                                        }
1,975✔
284

630✔
285
                                        if (refs.empty()) {
316✔
286
                                                result.insert(key);
757✔
287
                                        }
504✔
288
                                }
946✔
289

840✔
290
                                for (auto key : result) {
1,513✔
291
                                        crossrefs.erase(key);
757✔
292
                                }
505✔
293

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

298
                                return result;
1,261✔
299
                        }
841✔
300

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

504✔
305
                                currency result{};
757✔
306

504✔
307
                                for (auto ref : refs) {
2,710✔
308
                                        auto it = fields.find(ref);
2,458✔
309
                                        if (it == fields.end()) {
820✔
UNCOV
310
                                                fmt::print(stderr, "{}: error: cannot find p{}\n",
×
311
                                                           label(key), ref);
UNCOV
312
                                                return;
×
313
                                        }
314

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

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

1,638✔
329
                                        result = result + *val;
820✔
330
                                }
1,639✔
331

504✔
332
                                tgt = result;
253✔
333
                        }
505✔
334

335
                        void remove_crossrefs() {
358✔
336
                                // print_crossrefs();
714✔
337
                                auto layer = get_crossrefs_layer();
1,072✔
338
                                while (!layer.empty()) {
421✔
339
                                        for (auto key : layer) {
820✔
340
                                                calculate_sum(key);
757✔
341
                                        }
505✔
342
                                        layer = get_crossrefs_layer();
190✔
343
                                }
127✔
344
                        }
1,191✔
345

346
                        mapped_value<calculated_value> calculate() {
358✔
347
                                for (auto& [key, field] : fields) {
6,595✔
348
                                        if (std::holds_alternative<compiled_value>(field)) {
2,080✔
349
                                                precalc(key, invalid_index,
6,721✔
350
                                                        std::get<compiled_value>(field));
6,048✔
351
                                        } else {
4,033✔
352
                                                size_t index = 0;
190✔
353
                                                for (auto& child :
190✔
354
                                                     std::get<std::vector<compiled_value>>(field)) {
505✔
355
                                                        precalc(key, index, child);
379✔
356
                                                        ++index;
379✔
357
                                                }
253✔
358
                                        }
126✔
359
                                }
4,159✔
360

714✔
361
                                remove_crossrefs();
1,072✔
362

714✔
363
                                mapped_value<calculated_value> result{};
1,072✔
364
                                for (auto const& [key, field] : fields) {
6,595✔
365
                                        result[key] = extract<calculated_value>(field);
6,238✔
366
                                }
4,159✔
367
                                return result;
1,072✔
368
                        }
715✔
369
                };
370
        }  // namespace
371

372
        calculated_block calculate(compiled_block const& self,
357✔
373
                                   global_object const& ctx,
374
                                   std::string_view log_name) {
1✔
375
                std::string extended_name;
1,072✔
376
                if (!self.id.empty()) {
358✔
377
                        extended_name = fmt::format("{}.{}", log_name, self.id);
337✔
378
                        log_name = extended_name;
253✔
379
                }
168✔
380
                return calculated_block{
953✔
381
                    .id = self.id,
1,071✔
382
                    .fields = data_calculator::calculate(log_name, self.fields, ctx)};
1,547✔
383
        }
1,072✔
384

385
        calculated_section calculate(compiled_section const& self,
294✔
386
                                     global_object const& ctx) {
1✔
387
                calculated_section result{.id = self.id, .repeatable = self.repeatable};
883✔
388
                result.blocks.reserve(self.blocks.size());
883✔
389
                for (auto const& block : self.blocks) {
1,366✔
390
                        result.blocks.push_back(calculate(block, ctx, self.id));
1,072✔
391
                }
715✔
392
                return result;
883✔
393
        }
589✔
394

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

406
        compiled_templates compiled_templates::compile(templates const& input) {
22✔
407
                compiled_templates result{};
64✔
408
                for (auto const& [key, report] : input.reports) {
148✔
409
                        result.reports[key] = compile_report(report);
127✔
410
                }
85✔
411
                return result;
64✔
412
        }
43✔
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