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

llnl / dftracer-utils / 27737850362

18 Jun 2026 05:01AM UTC coverage: 49.902% (-2.2%) from 52.111%
27737850362

Pull #79

github

web-flow
Merge 9372743c5 into 53ad1e86c
Pull Request #79: Add Valgrind memory checking (C++, Python, MPI) and fix the bugs it found

16075 of 43887 branches covered (36.63%)

Branch coverage included in aggregate %.

113 of 128 new or added lines in 11 files covered. (88.28%)

668 existing lines in 104 files now uncovered.

21472 of 31355 relevant lines covered (68.48%)

13060.03 hits per line

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

69.81
/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,667✔
30
    return std::isalnum(static_cast<unsigned char>(c)) || c == '_' || c == '.';
3,667!
31
}
32

33
bool iequals(std::string_view a, std::string_view b) {
6,312✔
34
    if (a.size() != b.size()) return false;
6,312✔
35
    return std::equal(a.begin(), a.end(), b.begin(),
2,115✔
36
                      [](unsigned char ca, unsigned char cb) {
2,602✔
37
                          return std::tolower(ca) == std::tolower(cb);
2,602✔
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;
609✔
59

60
    auto skip_ws = [&]() {
4,167✔
61
        while (pos < input.size() &&
13,080✔
62
               std::isspace(static_cast<unsigned char>(input[pos]))) {
6,234✔
63
            ++pos;
2,678✔
64
        }
65
    };
609✔
66

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

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

74
        // Operators
75
        if (c == '=' && pos + 1 < input.size() && input[pos + 1] == '=') {
3,559!
76
            tokens.push_back({TokenKind::OP_EQ, input.substr(start, 2), start});
806!
77
            pos += 2;
804✔
78
            continue;
804✔
79
        }
80
        if (c == '!' && pos + 1 < input.size() && input[pos + 1] == '=') {
2,755!
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,752!
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,722!
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,698✔
96
            tokens.push_back({TokenKind::OP_GT, input.substr(start, 1), start});
22!
97
            ++pos;
22✔
98
            continue;
22✔
99
        }
100
        if (c == '<') {
2,676✔
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,674✔
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,670✔
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,666✔
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,625✔
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,584✔
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,402!
139
            char quote = c;
977✔
140
            ++pos;
977✔
141
            while (pos < input.size() && input[pos] != quote) {
6,265✔
142
                if (input[pos] == '\\' && pos + 1 < input.size()) {
5,287!
143
                    pos += 2;
×
144
                } else {
145
                    ++pos;
5,288✔
146
                }
147
            }
148
            if (pos >= input.size()) {
977✔
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,
974!
154
                              input.substr(start + 1, pos - start - 2), start});
976!
155
            continue;
976✔
156
        }
976✔
157

158
        // Numbers (including negative)
159
        if (std::isdigit(static_cast<unsigned char>(c)) ||
1,426✔
160
            (c == '-' && pos + 1 < input.size() &&
2!
161
             std::isdigit(static_cast<unsigned char>(input[pos + 1])))) {
3✔
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,301✔
183
            ++pos;
1,301✔
184
            while (pos < input.size() && is_ident_char(input[pos])) {
3,676✔
185
                ++pos;
2,375✔
186
            }
187
            auto text = input.substr(start, pos - start);
1,300!
188
            TokenKind kind = TokenKind::IDENT;
1,298✔
189
            if (iequals(text, "and"))
1,298!
190
                kind = TokenKind::KW_AND;
85✔
191
            else if (iequals(text, "or"))
1,214!
192
                kind = TokenKind::KW_OR;
233✔
193
            else if (iequals(text, "not"))
980!
194
                kind = TokenKind::KW_NOT;
11✔
195
            else if (iequals(text, "in"))
971!
196
                kind = TokenKind::KW_IN;
41✔
197
            else if (iequals(text, "true"))
928!
198
                kind = TokenKind::KW_TRUE;
2✔
199
            else if (iequals(text, "false"))
928!
200
                kind = TokenKind::KW_FALSE;
2✔
201
            tokens.push_back({kind, text, start});
1,301!
202
            continue;
1,302✔
203
        }
1,302✔
204

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

209
    tokens.push_back(
607!
210
        {TokenKind::END, input.substr(input.size(), 0), input.size()});
607!
211
    return tokens;
607!
212
}
608✔
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)
593✔
223
        : tokens_(tokens), source_(source) {}
593✔
224

225
    dftracer::utils::expected<QueryNodePtr, QueryError> parse_query() {
591✔
226
        auto result = parse_or_expr();
591!
227
        if (!result) return result;
588✔
228
        if (current().kind != TokenKind::END) {
582!
229
            return dftracer::utils::unexpected(
×
230
                error("Expected 'and', 'or', or end of query, got '" +
×
231
                      std::string(current().text) + "'"));
×
232
        }
233
        return result;
582✔
234
    }
587✔
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,044✔
242

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

245
    bool match(TokenKind kind) {
1,206✔
246
        if (current().kind == kind) {
1,206✔
247
            ++pos_;
269✔
248
            return true;
269✔
249
        }
250
        return false;
939✔
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() {
596✔
260
        auto left = parse_and_expr();
596!
261
        if (!left) return left;
594✔
262
        while (current().kind == TokenKind::KW_OR) {
819✔
263
            advance();
231✔
264
            auto right = parse_and_expr();
231!
265
            if (!right) return right;
231!
266
            left = make_node(OrNode{std::move(*left), std::move(*right)});
231!
267
        }
231!
268
        return left;
587✔
269
    }
592✔
270

271
    dftracer::utils::expected<QueryNodePtr, QueryError> parse_and_expr() {
828✔
272
        auto left = parse_not_expr();
828!
273
        if (!left) return left;
824✔
274
        while (current().kind == TokenKind::KW_AND) {
895✔
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;
820✔
281
    }
822✔
282

283
    dftracer::utils::expected<QueryNodePtr, QueryError> parse_not_expr() {
909✔
284
        if (current().kind == TokenKind::KW_NOT) {
909✔
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();
903✔
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) {
899!
304
            return dftracer::utils::unexpected(
×
305
                error("Expected field name or '(', got '" +
×
306
                      std::string(current().text) + "'"));
×
307
        }
308

309
        auto field = parse_field();
899!
310

311
        // Check for "in" or "not in"
312
        if (current().kind == TokenKind::KW_IN) {
898✔
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) {
862✔
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();
857!
332
        if (!op) return dftracer::utils::unexpected(op.error());
859!
333
        auto val = parse_value();
859!
334
        if (!val) return dftracer::utils::unexpected(val.error());
856!
335
        return make_node(CompareNode{std::move(field), *op, std::move(*val)});
1,701!
336
    }
896✔
337

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

343
    dftracer::utils::expected<CompareOp, QueryError> parse_comp_op() {
858✔
344
        auto& tok = current();
858✔
345
        switch (tok.kind) {
857!
346
            case TokenKind::OP_EQ:
785✔
347
                advance();
785✔
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 '" +
×
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,079✔
375
            case TokenKind::STRING: {
968✔
376
                auto text = std::string(tok.text);
968!
377
                advance();
967✔
378
                return LiteralNode{std::move(text)};
1,936!
379
            }
966✔
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(
593✔
447
    const std::vector<Token>& tokens, std::string_view source) {
448
    Parser parser(tokens, source);
593✔
449
    return parser.parse_query();
1,180!
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());
594!
456
    return parse_tokens(*tokens, input);
591!
457
}
589✔
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