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

llnl / dftracer-utils / 26165774062

20 May 2026 01:29PM UTC coverage: 51.981% (-0.2%) from 52.2%
26165774062

Pull #68

github

web-flow
Merge a4eaed4d4 into 6c9aaa7c9
Pull Request #68: feat(aggregator): offset metrics, per-event-name system metrics, and time-bucket persistence

36911 of 92534 branches covered (39.89%)

Branch coverage included in aggregate %.

89 of 185 new or added lines in 8 files covered. (48.11%)

276 existing lines in 9 files now uncovered.

33359 of 42649 relevant lines covered (78.22%)

20407.26 hits per line

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

72.63
/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 {
4✔
12
    std::ostringstream os;
4!
13
    os << "Query parse error at column " << column << ":\n";
4!
14
    os << "  " << source << '\n';
4!
15
    os << "  " << indicator << '\n';
4!
16
    os << "  " << message << '\n';
4!
17
    return os.str();
6!
18
}
4✔
19

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

23
namespace {
24

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

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

33
bool iequals(std::string_view a, std::string_view b) {
12,653✔
34
    if (a.size() != b.size()) return false;
12,653✔
35
    return std::equal(a.begin(), a.end(), b.begin(),
4,238✔
36
                      [](unsigned char ca, unsigned char cb) {
5,209✔
37
                          return std::tolower(ca) == std::tolower(cb);
5,209✔
38
                      });
2,115✔
39
}
6,339✔
40

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

53
}  // namespace
54

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

60
    auto skip_ws = [&]() {
8,955✔
61
        while (pos < input.size() &&
24,092✔
62
               std::isspace(static_cast<unsigned char>(input[pos]))) {
12,463✔
63
            ++pos;
5,359✔
64
        }
65
    };
4,778✔
66

67
    while (true) {
4,183✔
68
        skip_ws();
8,346✔
69
        if (pos >= input.size()) break;
8,333✔
70

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

74
        // Operators
75
        if (c == '=' && pos + 1 < input.size() && input[pos + 1] == '=') {
7,119!
76
            tokens.push_back({TokenKind::OP_EQ, input.substr(start, 2), start});
1,613!
77
            pos += 2;
1,610✔
78
            continue;
1,610✔
79
        }
80
        if (c == '!' && pos + 1 < input.size() && input[pos + 1] == '=') {
5,506!
81
            tokens.push_back({TokenKind::OP_NE, input.substr(start, 2), start});
6!
82
            pos += 2;
6✔
83
            continue;
6✔
84
        }
85
        if (c == '>' && pos + 1 < input.size() && input[pos + 1] == '=') {
5,504!
86
            tokens.push_back({TokenKind::OP_GE, input.substr(start, 2), start});
60!
87
            pos += 2;
60✔
88
            continue;
60✔
89
        }
90
        if (c == '<' && pos + 1 < input.size() && input[pos + 1] == '=') {
5,442✔
91
            tokens.push_back({TokenKind::OP_LE, input.substr(start, 2), start});
48!
92
            pos += 2;
48✔
93
            continue;
48✔
94
        }
95
        if (c == '>') {
5,395✔
96
            tokens.push_back({TokenKind::OP_GT, input.substr(start, 1), start});
44!
97
            ++pos;
44✔
98
            continue;
44✔
99
        }
100
        if (c == '<') {
5,351✔
101
            tokens.push_back({TokenKind::OP_LT, input.substr(start, 1), start});
4!
102
            ++pos;
4✔
103
            continue;
4✔
104
        }
105

106
        // Punctuation
107
        if (c == '(') {
5,347✔
108
            tokens.push_back(
8!
109
                {TokenKind::LPAREN, input.substr(start, 1), start});
8!
110
            ++pos;
8✔
111
            continue;
8✔
112
        }
113
        if (c == ')') {
5,339✔
114
            tokens.push_back(
8!
115
                {TokenKind::RPAREN, input.substr(start, 1), start});
8!
116
            ++pos;
8✔
117
            continue;
8✔
118
        }
119
        if (c == '[') {
5,331✔
120
            tokens.push_back(
82!
121
                {TokenKind::LBRACKET, input.substr(start, 1), start});
82!
122
            ++pos;
82✔
123
            continue;
82✔
124
        }
125
        if (c == ']') {
5,249✔
126
            tokens.push_back(
82!
127
                {TokenKind::RBRACKET, input.substr(start, 1), start});
82!
128
            ++pos;
82✔
129
            continue;
82✔
130
        }
131
        if (c == ',') {
5,167✔
132
            tokens.push_back({TokenKind::COMMA, input.substr(start, 1), start});
364!
133
            ++pos;
364✔
134
            continue;
364✔
135
        }
136

137
        // Strings
138
        if (c == '"' || c == '\'') {
4,803✔
139
            char quote = c;
1,952✔
140
            ++pos;
1,952✔
141
            while (pos < input.size() && input[pos] != quote) {
12,530✔
142
                if (input[pos] == '\\' && pos + 1 < input.size()) {
10,574!
143
                    pos += 2;
×
144
                } else {
145
                    ++pos;
10,578✔
146
                }
147
            }
148
            if (pos >= input.size()) {
1,959✔
149
                return dftracer::utils::unexpected(make_error(
3!
150
                    input, start, pos - start, "Unterminated string literal"));
2!
151
            }
152
            ++pos;  // consume closing quote
1,950✔
153
            tokens.push_back({TokenKind::STRING,
1,951✔
154
                              input.substr(start + 1, pos - start - 2), start});
1,950!
155
            continue;
1,954✔
156
        }
976✔
157

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

181
        // Identifiers and keywords
182
        if (is_ident_start(c)) {
2,604✔
183
            ++pos;
2,600✔
184
            while (pos < input.size() && is_ident_char(input[pos])) {
8,646✔
185
                ++pos;
4,743✔
186
            }
187
            auto text = input.substr(start, pos - start);
2,603!
188
            TokenKind kind = TokenKind::IDENT;
2,603✔
189
            if (iequals(text, "and"))
2,603✔
190
                kind = TokenKind::KW_AND;
170✔
191
            else if (iequals(text, "or"))
2,431✔
192
                kind = TokenKind::KW_OR;
466✔
193
            else if (iequals(text, "not"))
1,968!
194
                kind = TokenKind::KW_NOT;
22✔
195
            else if (iequals(text, "in"))
1,943!
196
                kind = TokenKind::KW_IN;
82✔
197
            else if (iequals(text, "true"))
1,865!
198
                kind = TokenKind::KW_TRUE;
4✔
199
            else if (iequals(text, "false"))
1,858!
200
                kind = TokenKind::KW_FALSE;
4✔
201
            tokens.push_back({kind, text, start});
2,604✔
202
            continue;
2,604✔
203
        }
1,302✔
204

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

209
    tokens.push_back(
1,216✔
210
        {TokenKind::END, input.substr(input.size(), 0), input.size()});
1,216!
211
    return tokens;
1,215✔
212
}
1,241✔
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)
1,780✔
223
        : tokens_(tokens), source_(source) {}
2,375✔
224

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

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

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

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

245
    bool match(TokenKind kind) {
2,419✔
246
        if (current().kind == kind) {
2,419✔
247
            ++pos_;
538✔
248
            return true;
538✔
249
        }
250
        return false;
1,881✔
251
    }
1,210✔
252

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

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

271
    dftracer::utils::expected<QueryNodePtr, QueryError> parse_and_expr() {
1,659✔
272
        auto left = parse_not_expr();
1,659!
273
        if (!left) return left;
1,662✔
274
        while (current().kind == TokenKind::KW_AND) {
1,802✔
275
            advance();
152!
276
            auto right = parse_not_expr();
152!
277
            if (!right) return right;
152✔
278
            left = make_node(AndNode{std::move(*left), std::move(*right)});
152!
279
        }
152✔
280
        return left;
1,650✔
281
    }
1,660✔
282

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

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

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

309
        auto field = parse_field();
1,802!
310

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

330
        // Comparison
331
        auto op = parse_comp_op();
1,724!
332
        if (!op) return dftracer::utils::unexpected(op.error());
1,721!
333
        auto val = parse_value();
1,721!
334
        if (!val) return dftracer::utils::unexpected(val.error());
1,725!
335
        return make_node(CompareNode{std::move(field), *op, std::move(*val)});
2,566!
336
    }
1,809✔
337

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

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

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

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

444
}  // namespace
445

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

452
dftracer::utils::expected<QueryNodePtr, QueryError> parse(
1,192✔
453
    std::string_view input) {
454
    auto tokens = tokenize(input);
1,192!
455
    if (!tokens) return dftracer::utils::unexpected(tokens.error());
1,190!
456
    return parse_tokens(*tokens, input);
1,183!
457
}
1,193✔
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