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

mbits-os / quick_dra / 22241471141

20 Feb 2026 09:14PM UTC coverage: 98.4% (-0.3%) from 98.659%
22241471141

Pull #42

github

web-flow
Merge 592ea7635 into 56bca2562
Pull Request #42: ci: adding coverage comment to GitHub PR

4306 of 4376 relevant lines covered (98.4%)

2394.2 hits per line

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

94.9
/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 {
16
                                return compile_field(field);
12,672✔
17
                        }
9,504✔
18
                        maybe_list<compiled_value> operator()(
99✔
19
                            std::vector<std::string> const& fields) const noexcept {
20
                                std::vector<compiled_value> result{};
396✔
21
                                result.reserve(fields.size());
396✔
22
                                std::transform(fields.begin(), fields.end(),
396✔
23
                                               std::back_inserter(result), compile_field);
297✔
24
                                return result;
396✔
25
                        }
396✔
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) {
396✔
34
                        auto const parts = split_sv(input.substr(2), ','_sep);
1,584✔
35
                        std::vector<unsigned> refs{};
1,584✔
36
                        refs.reserve(parts.size());
1,584✔
37

1,188✔
38
                        for (auto const& part : parts) {
5,544✔
39
                                auto const trimmed = strip_sv(part);
5,148✔
40
                                unsigned ref{};
5,148✔
41

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

3,861✔
50
                                refs.push_back(ref);
1,287✔
51
                        }
3,861✔
52
                        return addition{.refs = std::move(refs)};
396✔
53
                }
1,584✔
54

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

3,564✔
64
                        return value;
1,188✔
65
                }
3,564✔
66

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

5,247✔
73
                                return varname::parse(input);
1,749✔
74
                        }
6,435✔
75

3,663✔
76
                        if (input.ends_with("zł")) {
1,221✔
77
                                return compile_currency(input);
4,752✔
78
                        }
3,564✔
79

99✔
80
                        return std::string{input.data(), input.size()};
99✔
81
                }
3,663✔
82

83
                compiled_block compile_block(report_section const& input) {
561✔
84
                        compiled_block result{};
2,244✔
85

1,683✔
86
                        if (input.block) {
561✔
87
                                result.id = *input.block;
528✔
88
                        }
396✔
89

1,683✔
90
                        for (auto const& [index, field] : input.fields) {
13,629✔
91
                                result.fields[index] = std::visit(field_compiler{}, field);
13,068✔
92
                        }
9,801✔
93

1,683✔
94
                        return result;
2,244✔
95
                }  // GCOV_EXCL_LINE[GCC]
1,683✔
96

97
                std::vector<compiled_section> compile_report(
66✔
98
                    std::vector<report_section> const& input) {
99
                        std::map<std::string, section_stats> stats{};
264✔
100
                        for (auto const& section : input) {
2,442✔
101
                                auto it = stats.lower_bound(section.id);
2,376✔
102
                                if (it == stats.end() || it->first != section.id) {
594✔
103
                                        it = stats.insert(it, {section.id, section_stats{}});
1,848✔
104
                                }
1,386✔
105

1,782✔
106
                                auto& stat = it->second;
2,376✔
107

1,782✔
108
                                ++stat.block_count;
2,376✔
109
                                stat.repeatable |= section.repeatable.value_or(false);
2,376✔
110
                        }
1,782✔
111
                        std::vector<compiled_section> result{};
264✔
112
                        result.reserve(stats.size());
264✔
113
                        for (auto const& section : input) {
2,442✔
114
                                if (section.fields.empty() && !section.block) continue;
594✔
115

1,683✔
116
                                auto it = std::find_if(
748✔
117
                                    result.begin(), result.end(),
1,683✔
118
                                    [&id = section.id](compiled_section const& out) {
561✔
119
                                            return out.id == id;
7,920✔
120
                                    });
5,940✔
121

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

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

1,386✔
132
                                        result.push_back({section.id, repeatable});
2,002✔
133

1,386✔
134
                                        if (block_count) {
462✔
135
                                                result.back().blocks.reserve(block_count);
1,848✔
136
                                        }
1,386✔
137

1,386✔
138
                                        it = std::prev(result.end());
2,310✔
139
                                }
1,386✔
140

1,683✔
141
                                auto& output = *it;
2,244✔
142
                                output.blocks.push_back(compile_block(section));
2,244✔
143
                        }
1,683✔
144
                        return result;
286✔
145
                }
726✔
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{};
152

153
                        static mapped_value<calculated_value> calculate(
573✔
154
                            std::string_view id,
155
                            mapped_value<compiled_value> const& fields,
156
                            global_object const& ctx) {
157
                                data_calculator src{id, ctx, fields};
2,292✔
158
                                return src.calculate();
2,865✔
159
                        }
2,292✔
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,301✔
168
                                        return value;
21,204✔
169
                                }
15,903✔
170

171
                                To operator()(addition const&) const noexcept { return {}; }
3✔
172
                                To operator()(varname const&) const noexcept { return {}; }
3✔
173

174
                                maybe_list<To> operator()(From const& field) const noexcept {
4,704✔
175
                                        return std::visit(*this, field);
18,816✔
176
                                }
14,112✔
177

178
                                maybe_list<To> operator()(
168✔
179
                                    std::vector<From> const& fields) const noexcept {
180
                                        std::vector<To> result{};
672✔
181
                                        result.reserve(fields.size());
672✔
182
                                        for (auto const& field : fields) {
1,788✔
183
                                                result.push_back(std::visit(*this, field));
1,620✔
184
                                        }
1,215✔
185
                                        return result;
672✔
186
                                }
672✔
187
                        };
188

189
                        template <typename To, typename From>
190
                        maybe_list<To> extract(maybe_list<From> const& val) {
4,872✔
191
                                return std::visit(value_extractor<From, To>{}, val);
19,488✔
192
                        }
14,616✔
193

194
                        template <typename To, typename From>
195
                        To extract(From const& val) {
198✔
196
                                return std::visit(value_extractor<From, To>{}, val);
792✔
197
                        }
594✔
198

199
                        inline std::string label(unsigned key,
6✔
200
                                                 size_t index = invalid_index) {
201
                                return fmt::format(
20✔
202
                                    fmt::runtime(index == invalid_index ? "{} p{}"
6✔
203
                                                                        : "{} p{}.{}"),
204
                                    id, key, index);
34✔
205
                        }  // GCOV_EXCL_LINE[WIN32]
18✔
206

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

5,256✔
218
                                auto const& data = *ptr;
1,752✔
219

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

5,256✔
228
                                auto const& src = *data.value;
1,752✔
229
                                if (index == invalid_index) {
1,752✔
230
                                        fields.find(key)->second = extract<compiled_value>(src);
6,216✔
231
                                        return;
6,216✔
232
                                }
4,662✔
233

594✔
234
                                if (std::holds_alternative<calculated_value>(src)) {
198✔
235
                                        tgt = extract<compiled_value>(
990✔
236
                                            std::get<calculated_value>(src));
792✔
237
                                        return;
792✔
238
                                }
594✔
239

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

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

5,004✔
252
                                if (std::holds_alternative<addition>(tgt)) {
1,668✔
253
                                        auto const& refs = std::get<addition>(tgt).refs;
1,608✔
254

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

1,206✔
263
                                        crossrefs[key] =
402✔
264
                                            std::set<unsigned>{refs.begin(), refs.end()};
2,010✔
265
                                }
1,206✔
266
                        }
5,004✔
267

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

274
                        std::set<unsigned> get_crossrefs_layer() {
675✔
275
                                std::set<unsigned> result{};
2,700✔
276

2,025✔
277
                                for (auto& [key, refs] : crossrefs) {
3,201✔
278
                                        auto copy = refs;
2,004✔
279
                                        for (auto ref : copy) {
6,777✔
280
                                                auto it = crossrefs.find(ref);
6,276✔
281
                                                if (it == crossrefs.end()) {
1,569✔
282
                                                        refs.erase(ref);
5,220✔
283
                                                }
3,915✔
284
                                        }
4,707✔
285

1,503✔
286
                                        if (refs.empty()) {
501✔
287
                                                result.insert(key);
1,608✔
288
                                        }
1,206✔
289
                                }
2,004✔
290

2,025✔
291
                                for (auto key : result) {
3,102✔
292
                                        crossrefs.erase(key);
1,608✔
293
                                }
1,206✔
294

2,025✔
295
                                return result;
2,700✔
296
                        }  // GCOV_EXCL_LINE[GCC]
2,025✔
297

298
                        void calculate_sum(unsigned key) {
402✔
299
                                auto& tgt = std::get<compiled_value>(fields.find(key)->second);
1,608✔
300
                                auto& refs = std::get<addition>(tgt).refs;
1,608✔
301

1,206✔
302
                                currency result{};
1,608✔
303

1,206✔
304
                                for (auto ref : refs) {
5,607✔
305
                                        auto it = fields.find(ref);
5,208✔
306
                                        if (it == fields.end()) {
1,302✔
307
                                                fmt::print(stderr, "{}: error: cannot find p{}\n",
×
308
                                                           label(key), ref);
×
309
                                                return;
2✔
310
                                        }
311

3,906✔
312
                                        auto src = std::get_if<compiled_value>(&it->second);
1,302✔
313
                                        if (!src) {
1,302✔
314
                                                fmt::print(stderr, "{}: error: p{} is not a scalar\n",
×
315
                                                           label(key), ref);
×
316
                                                return;
×
317
                                        }
318

3,906✔
319
                                        auto val = std::get_if<currency>(src);
1,302✔
320
                                        if (!val) {
1,302✔
321
                                                fmt::print(stderr, "{}: error: p{} is not a number\n",
12✔
322
                                                           label(key), ref);
15✔
323
                                                return;
12✔
324
                                        }
9✔
325

3,897✔
326
                                        result = result + *val;
1,299✔
327
                                }
3,897✔
328

1,197✔
329
                                tgt = result;
399✔
330
                        }
1,197✔
331

332
                        void remove_crossrefs() {
573✔
333
                                // print_crossrefs();
1,719✔
334
                                auto layer = get_crossrefs_layer();
2,292✔
335
                                while (!layer.empty()) {
675✔
336
                                        for (auto key : layer) {
1,710✔
337
                                                calculate_sum(key);
1,608✔
338
                                        }
1,206✔
339
                                        layer = get_crossrefs_layer();
408✔
340
                                }
306✔
341
                        }
2,483✔
342

343
                        mapped_value<calculated_value> calculate() {
573✔
344
                                for (auto& [key, field] : fields) {
13,845✔
345
                                        if (std::holds_alternative<compiled_value>(field)) {
3,318✔
346
                                                precalc(key, invalid_index,
13,936✔
347
                                                        std::get<compiled_value>(field));
12,864✔
348
                                        } else {
9,648✔
349
                                                size_t index = 0;
408✔
350
                                                for (auto& child :
408✔
351
                                                     std::get<std::vector<compiled_value>>(field)) {
1,032✔
352
                                                        precalc(key, index, child);
828✔
353
                                                        ++index;
828✔
354
                                                }
621✔
355
                                        }
306✔
356
                                }
9,954✔
357

1,719✔
358
                                remove_crossrefs();
2,292✔
359

1,719✔
360
                                mapped_value<calculated_value> result{};
2,292✔
361
                                for (auto const& [key, field] : fields) {
13,845✔
362
                                        result[key] = extract<calculated_value>(field);
13,272✔
363
                                }
9,954✔
364
                                return result;
2,292✔
365
                        }  // GCOV_EXCL_LINE[GCC]
1,719✔
366
                };
367
        }  // namespace
368

369
        calculated_block calculate(compiled_block const& self,
573✔
370
                                   global_object const& ctx,
371
                                   std::string_view log_name) {
372
                std::string extended_name;
2,292✔
373
                if (!self.id.empty()) {
573✔
374
                        extended_name = fmt::format("{}.{}", log_name, self.id);
690✔
375
                        log_name = extended_name;
552✔
376
                }
414✔
377
                // GCOV_EXCL_START[GCC]
1,719✔
378
                return calculated_block{
1,719✔
379
                    // GCOV_EXCL_STOP
1,719✔
380
                    .id = self.id,
2,292✔
381
                    .fields = data_calculator::calculate(log_name, self.fields, ctx)};
3,056✔
382
        }
2,292✔
383

384
        calculated_section calculate(compiled_section const& self,
468✔
385
                                     global_object const& ctx) {
386
                calculated_section result{.id = self.id, .repeatable = self.repeatable};
1,872✔
387
                result.blocks.reserve(self.blocks.size());
1,872✔
388
                for (auto const& block : self.blocks) {
2,760✔
389
                        result.blocks.push_back(calculate(block, ctx, self.id));
2,292✔
390
                }
1,719✔
391
                return result;
1,872✔
392
        }  // GCOV_EXCL_LINE[GCC]
1,404✔
393

394
        std::vector<calculated_section> calculate(
69✔
395
            std::vector<compiled_section> const& report,
396
            global_object const& ctx) {
397
                std::vector<calculated_section> result{};
276✔
398
                result.reserve(report.size());
276✔
399
                for (auto const& section : report) {
1,941✔
400
                        result.push_back(calculate(section, ctx));
1,872✔
401
                }
1,404✔
402
                return result;
276✔
403
        }  // GCOV_EXCL_LINE[GCC]
207✔
404

405
        compiled_templates compiled_templates::compile(templates const& input) {
33✔
406
                compiled_templates result{};
132✔
407
                for (auto const& [key, report] : input.reports) {
297✔
408
                        result.reports[key] = compile_report(report);
264✔
409
                }
198✔
410
                return result;
132✔
411
        }  // GCOV_EXCL_LINE[GCC]
99✔
412
}  // 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