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

llnl / dftracer-utils / 26195612357

20 May 2026 11:19PM UTC coverage: 49.859% (-2.3%) from 52.2%
26195612357

push

github

hariharan-devarajan
feat(aggregator): improve system metrics scanning and persistence error handling

16041 of 43831 branches covered (36.6%)

Branch coverage included in aggregate %.

6 of 17 new or added lines in 2 files covered. (35.29%)

1072 existing lines in 104 files now uncovered.

21423 of 31309 relevant lines covered (68.42%)

13054.31 hits per line

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

70.03
/src/dftracer/utils/utilities/common/query/parser.cpp
1
#include <dftracer/utils/utilities/common/query/parser.h>
2

3
#include <algorithm>
4
#include <cctype>
5
#include <charconv>
6
#include <sstream>
7
#include <string>
8

9
namespace dftracer::utils::utilities::common::query {
10

11
std::string QueryError::format() const {
2✔
12
    std::ostringstream os;
2!
13
    os << "Query parse error at column " << column << ":\n";
2!
14
    os << "  " << source << '\n';
2!
15
    os << "  " << indicator << '\n';
2!
16
    os << "  " << message << '\n';
2!
17
    return os.str();
4!
18
}
2✔
19

20
QueryParseError::QueryParseError(QueryError err)
1✔
21
    : std::runtime_error(err.format()), err_(std::move(err)) {}
1!
22

23
namespace {
24

25
bool is_ident_start(char c) {
1,302✔
26
    return std::isalpha(static_cast<unsigned char>(c)) || c == '_';
1,302!
27
}
28

29
bool is_ident_char(char c) {
3,664✔
30
    return std::isalnum(static_cast<unsigned char>(c)) || c == '_' || c == '.';
3,664!
31
}
32

33
bool iequals(std::string_view a, std::string_view b) {
6,300✔
34
    if (a.size() != b.size()) return false;
6,300✔
35
    return std::equal(a.begin(), a.end(), b.begin(),
2,111✔
36
                      [](unsigned char ca, unsigned char cb) {
2,600✔
37
                          return std::tolower(ca) == std::tolower(cb);
2,600✔
38
                      });
2,116✔
39
}
40

41
QueryError make_error(std::string_view source, std::size_t col, std::size_t len,
7✔
42
                      std::string msg) {
43
    std::string ind(col, ' ');
7!
44
    if (len > 0) {
7!
45
        ind += '^';
7!
46
        for (std::size_t i = 1; i < len; ++i) ind += '~';
12!
47
    } else {
48
        ind += '^';
×
49
    }
50
    return QueryError{std::move(msg), col, std::string(source), std::move(ind)};
14!
51
}
7✔
52

53
}  // namespace
54

55
dftracer::utils::expected<std::vector<Token>, QueryError> tokenize(
609✔
56
    std::string_view input) {
57
    std::vector<Token> tokens;
609✔
58
    std::size_t pos = 0;
607✔
59

60
    auto skip_ws = [&]() {
4,158✔
61
        while (pos < input.size() &&
13,053✔
62
               std::isspace(static_cast<unsigned char>(input[pos]))) {
6,217✔
63
            ++pos;
2,673✔
64
        }
65
    };
607✔
66

67
    while (true) {
68
        skip_ws();
4,160✔
69
        if (pos >= input.size()) break;
4,158✔
70

71
        std::size_t start = pos;
3,554✔
72
        char c = input[pos];
3,554✔
73

74
        // Operators
75
        if (c == '=' && pos + 1 < input.size() && input[pos + 1] == '=') {
3,555!
76
            tokens.push_back({TokenKind::OP_EQ, input.substr(start, 2), start});
804!
77
            pos += 2;
803✔
78
            continue;
803✔
79
        }
80
        if (c == '!' && pos + 1 < input.size() && input[pos + 1] == '=') {
2,751!
81
            tokens.push_back({TokenKind::OP_NE, input.substr(start, 2), start});
3!
82
            pos += 2;
3✔
83
            continue;
3✔
84
        }
85
        if (c == '>' && pos + 1 < input.size() && input[pos + 1] == '=') {
2,750!
86
            tokens.push_back({TokenKind::OP_GE, input.substr(start, 2), start});
30!
87
            pos += 2;
30✔
88
            continue;
30✔
89
        }
90
        if (c == '<' && pos + 1 < input.size() && input[pos + 1] == '=') {
2,719✔
91
            tokens.push_back({TokenKind::OP_LE, input.substr(start, 2), start});
24!
92
            pos += 2;
24✔
93
            continue;
24✔
94
        }
95
        if (c == '>') {
2,697✔
96
            tokens.push_back({TokenKind::OP_GT, input.substr(start, 1), start});
22!
97
            ++pos;
22✔
98
            continue;
22✔
99
        }
100
        if (c == '<') {
2,675✔
101
            tokens.push_back({TokenKind::OP_LT, input.substr(start, 1), start});
2!
102
            ++pos;
2✔
103
            continue;
2✔
104
        }
105

106
        // Punctuation
107
        if (c == '(') {
2,673✔
108
            tokens.push_back(
4!
109
                {TokenKind::LPAREN, input.substr(start, 1), start});
4!
110
            ++pos;
4✔
111
            continue;
4✔
112
        }
113
        if (c == ')') {
2,669✔
114
            tokens.push_back(
4!
115
                {TokenKind::RPAREN, input.substr(start, 1), start});
4!
116
            ++pos;
4✔
117
            continue;
4✔
118
        }
119
        if (c == '[') {
2,665✔
120
            tokens.push_back(
41!
121
                {TokenKind::LBRACKET, input.substr(start, 1), start});
41!
122
            ++pos;
41✔
123
            continue;
41✔
124
        }
125
        if (c == ']') {
2,624✔
126
            tokens.push_back(
41!
127
                {TokenKind::RBRACKET, input.substr(start, 1), start});
41!
128
            ++pos;
41✔
129
            continue;
41✔
130
        }
131
        if (c == ',') {
2,583✔
132
            tokens.push_back({TokenKind::COMMA, input.substr(start, 1), start});
182!
133
            ++pos;
182✔
134
            continue;
182✔
135
        }
136

137
        // Strings
138
        if (c == '"' || c == '\'') {
2,401✔
139
            char quote = c;
977✔
140
            ++pos;
977✔
141
            while (pos < input.size() && input[pos] != quote) {
6,254✔
142
                if (input[pos] == '\\' && pos + 1 < input.size()) {
5,277!
143
                    pos += 2;
×
144
                } else {
145
                    ++pos;
5,277✔
146
                }
147
            }
148
            if (pos >= input.size()) {
974✔
149
                return dftracer::utils::unexpected(make_error(
2!
150
                    input, start, pos - start, "Unterminated string literal"));
1✔
151
            }
152
            ++pos;  // consume closing quote
976✔
153
            tokens.push_back({TokenKind::STRING,
975!
154
                              input.substr(start + 1, pos - start - 2), start});
976!
155
            continue;
973✔
156
        }
973✔
157

158
        // Numbers (including negative)
159
        if (std::isdigit(static_cast<unsigned char>(c)) ||
1,427✔
160
            (c == '-' && pos + 1 < input.size() &&
2✔
161
             std::isdigit(static_cast<unsigned char>(input[pos + 1])))) {
1!
162
            bool is_float = false;
123✔
163
            ++pos;
123✔
164
            while (pos < input.size() &&
1,036✔
165
                   (std::isdigit(static_cast<unsigned char>(input[pos])) ||
484✔
166
                    input[pos] == '.' || input[pos] == 'e' ||
59✔
167
                    input[pos] == 'E' || input[pos] == '+' ||
55!
168
                    input[pos] == '-')) {
55!
169
                if (input[pos] == '.' || input[pos] == 'e' ||
854✔
170
                    input[pos] == 'E') {
425!
171
                    is_float = true;
4✔
172
                }
173
                ++pos;
429✔
174
            }
175
            auto text = input.substr(start, pos - start);
123!
176
            tokens.push_back(
123!
177
                {is_float ? TokenKind::FLOAT : TokenKind::INT, text, start});
123✔
178
            continue;
123✔
179
        }
123✔
180

181
        // Identifiers and keywords
182
        if (is_ident_start(c)) {
1,302✔
183
            ++pos;
1,300✔
184
            while (pos < input.size() && is_ident_char(input[pos])) {
3,672✔
185
                ++pos;
2,372✔
186
            }
187
            auto text = input.substr(start, pos - start);
1,303!
188
            TokenKind kind = TokenKind::IDENT;
1,302✔
189
            if (iequals(text, "and"))
1,302!
190
                kind = TokenKind::KW_AND;
85✔
191
            else if (iequals(text, "or"))
1,217!
192
                kind = TokenKind::KW_OR;
233✔
193
            else if (iequals(text, "not"))
984!
194
                kind = TokenKind::KW_NOT;
11✔
195
            else if (iequals(text, "in"))
970!
196
                kind = TokenKind::KW_IN;
41✔
197
            else if (iequals(text, "true"))
929!
198
                kind = TokenKind::KW_TRUE;
2✔
199
            else if (iequals(text, "false"))
927!
200
                kind = TokenKind::KW_FALSE;
2✔
201
            tokens.push_back({kind, text, start});
1,300!
202
            continue;
1,301✔
203
        }
1,301✔
204

205
        return dftracer::utils::unexpected(make_error(
2!
206
            input, start, 1, std::string("Unexpected character '") + c + "'"));
3!
207
    }
3,553✔
208

209
    tokens.push_back(
603!
210
        {TokenKind::END, input.substr(input.size(), 0), input.size()});
605!
211
    return tokens;
605!
212
}
607✔
213

214
// ============================================================
215
// Recursive Descent Parser
216
// ============================================================
217

218
namespace {
219

220
class Parser {
221
   public:
222
    Parser(const std::vector<Token>& tokens, std::string_view source)
590✔
223
        : tokens_(tokens), source_(source) {}
590✔
224

225
    dftracer::utils::expected<QueryNodePtr, QueryError> parse_query() {
590✔
226
        auto result = parse_or_expr();
590!
227
        if (!result) return result;
593✔
228
        if (current().kind != TokenKind::END) {
587!
229
            return dftracer::utils::unexpected(
×
230
                error("Expected 'and', 'or', or end of query, got '" +
×
231
                      std::string(current().text) + "'"));
×
232
        }
233
        return result;
587✔
234
    }
593✔
235

236
   private:
237
    const std::vector<Token>& tokens_;
238
    std::string_view source_;
239
    std::size_t pos_ = 0;
240

241
    const Token& current() const { return tokens_[pos_]; }
9,064✔
242

243
    const Token& advance() { return tokens_[pos_++]; }
3,188✔
244

245
    bool match(TokenKind kind) {
1,207✔
246
        if (current().kind == kind) {
1,207✔
247
            ++pos_;
269✔
248
            return true;
269✔
249
        }
250
        return false;
937✔
251
    }
252

253
    QueryError error(std::string msg) const {
5✔
254
        auto& tok = current();
5✔
255
        std::size_t len = tok.text.empty() ? 1 : tok.text.size();
5!
256
        return make_error(source_, tok.column, len, std::move(msg));
5!
257
    }
258

259
    dftracer::utils::expected<QueryNodePtr, QueryError> parse_or_expr() {
595✔
260
        auto left = parse_and_expr();
595!
261
        if (!left) return left;
597✔
262
        while (current().kind == TokenKind::KW_OR) {
823✔
263
            advance();
232✔
264
            auto right = parse_and_expr();
232!
265
            if (!right) return right;
232!
266
            left = make_node(OrNode{std::move(*left), std::move(*right)});
232!
267
        }
231!
268
        return left;
590✔
269
    }
595✔
270

271
    dftracer::utils::expected<QueryNodePtr, QueryError> parse_and_expr() {
826✔
272
        auto left = parse_not_expr();
826!
273
        if (!left) return left;
828✔
274
        while (current().kind == TokenKind::KW_AND) {
900✔
275
            advance();
76✔
276
            auto right = parse_not_expr();
76!
277
            if (!right) return right;
76!
278
            left = make_node(AndNode{std::move(*left), std::move(*right)});
76!
279
        }
76!
280
        return left;
824✔
281
    }
830✔
282

283
    dftracer::utils::expected<QueryNodePtr, QueryError> parse_not_expr() {
907✔
284
        if (current().kind == TokenKind::KW_NOT) {
907✔
285
            advance();
5✔
286
            auto operand = parse_not_expr();
5!
287
            if (!operand) return operand;
5!
288
            return make_node(NotNode{std::move(*operand)});
10!
289
        }
5✔
290
        return parse_primary();
901✔
291
    }
292

293
    dftracer::utils::expected<QueryNodePtr, QueryError> parse_primary() {
902✔
294
        if (match(TokenKind::LPAREN)) {
902✔
295
            auto expr = parse_or_expr();
4!
296
            if (!expr) return expr;
4!
297
            if (!match(TokenKind::RPAREN)) {
4!
298
                return dftracer::utils::unexpected(error("Expected ')'"));
×
299
            }
300
            return expr;
4✔
301
        }
4✔
302

303
        if (current().kind != TokenKind::IDENT) {
896!
304
            return dftracer::utils::unexpected(
×
305
                error("Expected field name or '(', got '" +
×
306
                      std::string(current().text) + "'"));
×
307
        }
308

309
        auto field = parse_field();
898!
310

311
        // Check for "in" or "not in"
312
        if (current().kind == TokenKind::KW_IN) {
899✔
313
            advance();
35✔
314
            auto arr = parse_array();
35!
315
            if (!arr) return dftracer::utils::unexpected(arr.error());
35!
316
            return make_node(InNode{std::move(field), std::move(*arr)});
70!
317
        }
35✔
318
        if (current().kind == TokenKind::KW_NOT) {
864✔
319
            // Look ahead for "in"
320
            if (pos_ + 1 < tokens_.size() &&
10!
321
                tokens_[pos_ + 1].kind == TokenKind::KW_IN) {
5!
322
                advance();  // consume "not"
5✔
323
                advance();  // consume "in"
5✔
324
                auto arr = parse_array();
5!
325
                if (!arr) return dftracer::utils::unexpected(arr.error());
5!
326
                return make_node(NotInNode{std::move(field), std::move(*arr)});
10!
327
            }
5✔
328
        }
329

330
        // Comparison
331
        auto op = parse_comp_op();
860!
332
        if (!op) return dftracer::utils::unexpected(op.error());
859!
333
        auto val = parse_value();
860!
334
        if (!val) return dftracer::utils::unexpected(val.error());
860!
335
        return make_node(CompareNode{std::move(field), *op, std::move(*val)});
1,710!
336
    }
900✔
337

338
    FieldNode parse_field() {
898✔
339
        auto& tok = advance();
898✔
340
        return FieldNode{std::string(tok.text)};
898!
341
    }
342

343
    dftracer::utils::expected<CompareOp, QueryError> parse_comp_op() {
859✔
344
        auto& tok = current();
859✔
345
        switch (tok.kind) {
859!
346
            case TokenKind::OP_EQ:
786✔
347
                advance();
786✔
348
                return CompareOp::EQ;
786!
349
            case TokenKind::OP_NE:
2✔
350
                advance();
2✔
351
                return CompareOp::NE;
2!
352
            case TokenKind::OP_GT:
18✔
353
                advance();
18✔
354
                return CompareOp::GT;
18!
355
            case TokenKind::OP_LT:
1✔
356
                advance();
1✔
357
                return CompareOp::LT;
1!
358
            case TokenKind::OP_GE:
29✔
359
                advance();
29✔
360
                return CompareOp::GE;
29!
361
            case TokenKind::OP_LE:
23✔
362
                advance();
23✔
363
                return CompareOp::LE;
23!
UNCOV
364
            default:
×
365
                return dftracer::utils::unexpected(
×
366
                    error("Expected comparison operator (==, !=, >, "
×
367
                          "<, >=, <=), got '" +
×
UNCOV
368
                          std::string(tok.text) + "'"));
×
369
        }
370
    }
371

372
    dftracer::utils::expected<LiteralNode, QueryError> parse_value() {
1,080✔
373
        auto& tok = current();
1,080✔
374
        switch (tok.kind) {
1,081✔
375
            case TokenKind::STRING: {
970✔
376
                auto text = std::string(tok.text);
970!
377
                advance();
969✔
378
                return LiteralNode{std::move(text)};
1,940!
379
            }
969✔
380
            case TokenKind::INT: {
102✔
381
                int64_t val = 0;
102✔
382
                auto [ptr, ec] = std::from_chars(
102!
383
                    tok.text.data(), tok.text.data() + tok.text.size(), val);
102✔
384
                if (ec != std::errc{}) {
102!
385
                    return dftracer::utils::unexpected(error(
×
386
                        "Invalid integer: '" + std::string(tok.text) + "'"));
×
387
                }
388
                advance();
102✔
389
                if (val >= 0) {
102✔
390
                    return LiteralNode{static_cast<uint64_t>(val)};
202!
391
                }
392
                return LiteralNode{val};
2!
393
            }
394
            case TokenKind::FLOAT: {
2✔
395
                double val = 0;
2✔
396
                auto sv = tok.text;
2✔
397
                // from_chars for double not available on all
398
                // compilers; use stod
399
                try {
400
                    val = std::stod(std::string(sv));
2!
UNCOV
401
                } catch (...) {
×
402
                    return dftracer::utils::unexpected(
×
403
                        error("Invalid float: '" + std::string(sv) + "'"));
×
404
                }
×
405
                advance();
2✔
406
                return LiteralNode{val};
4!
407
            }
408
            case TokenKind::KW_TRUE:
1✔
409
                advance();
1✔
410
                return LiteralNode{true};
2!
411
            case TokenKind::KW_FALSE:
1✔
412
                advance();
1✔
413
                return LiteralNode{false};
2!
414
            default:
5✔
415
                return dftracer::utils::unexpected(
10✔
416
                    error("Expected value (string, number, or bool), "
10!
417
                          "got '" +
5!
418
                          std::string(tok.text) + "'"));
20!
419
        }
420
    }
421

422
    dftracer::utils::expected<ArrayNode, QueryError> parse_array() {
40✔
423
        if (!match(TokenKind::LBRACKET)) {
40!
424
            return dftracer::utils::unexpected(error("Expected '['"));
×
425
        }
426
        ArrayNode arr;
40✔
427
        if (current().kind != TokenKind::RBRACKET) {
40!
428
            auto val = parse_value();
40!
429
            if (!val) return dftracer::utils::unexpected(val.error());
40!
430
            arr.elements.push_back(std::move(*val));
40!
431
            while (match(TokenKind::COMMA)) {
221✔
432
                val = parse_value();
181!
433
                if (!val) return dftracer::utils::unexpected(val.error());
181!
434
                arr.elements.push_back(std::move(*val));
181!
435
            }
436
        }
40!
437
        if (!match(TokenKind::RBRACKET)) {
40!
438
            return dftracer::utils::unexpected(error("Expected ']' or ','"));
×
439
        }
440
        return arr;
40!
441
    }
40✔
442
};
443

444
}  // namespace
445

446
dftracer::utils::expected<QueryNodePtr, QueryError> parse_tokens(
591✔
447
    const std::vector<Token>& tokens, std::string_view source) {
448
    Parser parser(tokens, source);
591✔
449
    return parser.parse_query();
1,182!
450
}
451

452
dftracer::utils::expected<QueryNodePtr, QueryError> parse(
594✔
453
    std::string_view input) {
454
    auto tokens = tokenize(input);
594!
455
    if (!tokens) return dftracer::utils::unexpected(tokens.error());
593!
456
    return parse_tokens(*tokens, input);
590!
457
}
595✔
458

459
}  // namespace dftracer::utils::utilities::common::query
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