Coverage Report

Created: 2026-09-14 06:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/proc/self/cwd/parser/internal/lexer.cc
Line
Count
Source
1
// Copyright 2026 Google LLC
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
//     https://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14
15
#include "parser/internal/lexer.h"
16
17
#include <cstddef>
18
#include <cstdint>
19
#include <optional>
20
#include <string>
21
#include <string_view>
22
23
#include "absl/base/attributes.h"
24
#include "absl/base/no_destructor.h"
25
#include "absl/base/optimization.h"
26
#include "absl/container/flat_hash_map.h"
27
#include "absl/functional/function_ref.h"
28
#include "absl/log/absl_check.h"
29
#include "absl/strings/ascii.h"
30
#include "absl/strings/str_cat.h"
31
#include "absl/strings/string_view.h"
32
33
namespace cel::parser_internal {
34
35
namespace {
36
37
0
[[nodiscard]] bool IsIdentTrailing(char32_t c) {
38
0
  return c <= 0x7f && (absl::ascii_isdigit(static_cast<char>(c)) ||
39
0
                       absl::ascii_isalpha(static_cast<char>(c)) || c == '_');
40
0
}
41
42
0
[[nodiscard]] bool IsPlusOrMinus(char32_t c) { return c == '+' || c == '-'; }
43
44
[[nodiscard]] const absl::flat_hash_map<std::string_view, TokenType>&
45
0
Keywords() {
46
0
  static const absl::NoDestructor<
47
0
      absl::flat_hash_map<std::string_view, TokenType>>
48
0
      kKeywords({
49
0
          {"false", TokenType::kFalse},
50
0
          {"true", TokenType::kTrue},
51
0
          {"null", TokenType::kNull},
52
0
          {"in", TokenType::kIn},
53
0
          {"as", TokenType::kReservedWord},
54
0
          {"break", TokenType::kReservedWord},
55
0
          {"const", TokenType::kReservedWord},
56
0
          {"continue", TokenType::kReservedWord},
57
0
          {"else", TokenType::kReservedWord},
58
0
          {"for", TokenType::kReservedWord},
59
0
          {"function", TokenType::kReservedWord},
60
0
          {"if", TokenType::kReservedWord},
61
0
          {"import", TokenType::kReservedWord},
62
0
          {"let", TokenType::kReservedWord},
63
0
          {"loop", TokenType::kReservedWord},
64
0
          {"package", TokenType::kReservedWord},
65
0
          {"namespace", TokenType::kReservedWord},
66
0
          {"return", TokenType::kReservedWord},
67
0
          {"var", TokenType::kReservedWord},
68
0
          {"void", TokenType::kReservedWord},
69
0
          {"while", TokenType::kReservedWord},
70
0
      });
71
0
  return *kKeywords;
72
0
}
73
74
}  // namespace
75
76
0
std::string_view TokenTypeToString(TokenType type) {
77
0
  switch (type) {
78
0
    case TokenType::kError:
79
0
      return "error";
80
0
    case TokenType::kEnd:
81
0
      return "end";
82
0
    case TokenType::kWhitespace:
83
0
      return "whitespace";
84
0
    case TokenType::kComment:
85
0
      return "comment";
86
0
    case TokenType::kNull:
87
0
      return "null";
88
0
    case TokenType::kFalse:
89
0
      return "false";
90
0
    case TokenType::kTrue:
91
0
      return "true";
92
0
    case TokenType::kIn:
93
0
      return "in";
94
0
    case TokenType::kReservedWord:
95
0
      return "reserved_word";
96
0
    case TokenType::kInt:
97
0
      return "int";
98
0
    case TokenType::kUint:
99
0
      return "uint";
100
0
    case TokenType::kFloat:
101
0
      return "float";
102
0
    case TokenType::kString:
103
0
      return "string";
104
0
    case TokenType::kBytes:
105
0
      return "bytes";
106
0
    case TokenType::kIdent:
107
0
      return "ident";
108
0
    case TokenType::kLeftBracket:
109
0
      return "[";
110
0
    case TokenType::kRightBracket:
111
0
      return "]";
112
0
    case TokenType::kLeftBrace:
113
0
      return "{";
114
0
    case TokenType::kRightBrace:
115
0
      return "}";
116
0
    case TokenType::kLeftParen:
117
0
      return "(";
118
0
    case TokenType::kRightParen:
119
0
      return ")";
120
0
    case TokenType::kDot:
121
0
      return ".";
122
0
    case TokenType::kComma:
123
0
      return ",";
124
0
    case TokenType::kMinus:
125
0
      return "-";
126
0
    case TokenType::kPlus:
127
0
      return "+";
128
0
    case TokenType::kAsterisk:
129
0
      return "*";
130
0
    case TokenType::kSlash:
131
0
      return "/";
132
0
    case TokenType::kPercent:
133
0
      return "%";
134
0
    case TokenType::kQuestion:
135
0
      return "?";
136
0
    case TokenType::kColon:
137
0
      return ":";
138
0
    case TokenType::kExclamation:
139
0
      return "!";
140
0
    case TokenType::kEqual:
141
0
      return "=";
142
0
    case TokenType::kEqualEqual:
143
0
      return "==";
144
0
    case TokenType::kExclamationEqual:
145
0
      return "!=";
146
0
    case TokenType::kLess:
147
0
      return "<";
148
0
    case TokenType::kLessEqual:
149
0
      return "<=";
150
0
    case TokenType::kGreater:
151
0
      return ">";
152
0
    case TokenType::kGreaterEqual:
153
0
      return ">=";
154
0
    case TokenType::kLogicalAnd:
155
0
      return "&&";
156
0
    case TokenType::kLogicalOr:
157
0
      return "||";
158
0
    default:
159
0
      return "<unknown>";
160
0
  }
161
0
}
162
163
0
Token Lexer::Lex() {
164
0
  int32_t start = GetPosition();
165
0
  if (ABSL_PREDICT_FALSE(position_ >= content_.size())) {
166
0
    at_end_ = true;
167
0
    done_ = true;
168
0
    return MakeToken(TokenType::kEnd, start, start);
169
0
  }
170
0
  char32_t c = content_.at(position_);
171
0
  switch (c) {
172
0
    case '\f':
173
0
      ABSL_FALLTHROUGH_INTENDED;
174
0
    case '\v':
175
0
      ABSL_FALLTHROUGH_INTENDED;
176
0
    case '\t':
177
0
      ABSL_FALLTHROUGH_INTENDED;
178
0
    case '\r':
179
0
      ABSL_FALLTHROUGH_INTENDED;
180
0
    case '\n':
181
0
      ABSL_FALLTHROUGH_INTENDED;
182
0
    case ' ': {
183
0
      ConsumeWhitespace();
184
0
      return MakeToken(TokenType::kWhitespace, start, GetPosition());
185
0
    }
186
0
    case '.': {
187
0
      if (position_ + 1 < content_.size() &&
188
0
          content_.at(position_ + 1) <= 0x7f &&
189
0
          absl::ascii_isdigit(static_cast<char>(content_.at(position_ + 1)))) {
190
0
        return ConsumeNumericLiteral();
191
0
      }
192
0
      Advance(1);
193
0
      return MakeToken(TokenType::kDot, start, GetPosition());
194
0
    }
195
0
    case ',': {
196
0
      Advance(1);
197
0
      return MakeToken(TokenType::kComma, start, GetPosition());
198
0
    }
199
0
    case '!': {
200
0
      Advance(1);
201
0
      if (Consume('=')) {
202
0
        return MakeToken(TokenType::kExclamationEqual, start, GetPosition());
203
0
      }
204
0
      return MakeToken(TokenType::kExclamation, start, GetPosition());
205
0
    }
206
0
    case '?': {
207
0
      Advance(1);
208
0
      return MakeToken(TokenType::kQuestion, start, GetPosition());
209
0
    }
210
0
    case '(': {
211
0
      Advance(1);
212
0
      return MakeToken(TokenType::kLeftParen, start, GetPosition());
213
0
    }
214
0
    case ')': {
215
0
      Advance(1);
216
0
      return MakeToken(TokenType::kRightParen, start, GetPosition());
217
0
    }
218
0
    case '{': {
219
0
      Advance(1);
220
0
      return MakeToken(TokenType::kLeftBrace, start, GetPosition());
221
0
    }
222
0
    case '}': {
223
0
      Advance(1);
224
0
      return MakeToken(TokenType::kRightBrace, start, GetPosition());
225
0
    }
226
0
    case '[': {
227
0
      Advance(1);
228
0
      return MakeToken(TokenType::kLeftBracket, start, GetPosition());
229
0
    }
230
0
    case ']': {
231
0
      Advance(1);
232
0
      return MakeToken(TokenType::kRightBracket, start, GetPosition());
233
0
    }
234
0
    case '=': {
235
0
      Advance(1);
236
0
      if (Consume('=')) {
237
0
        return MakeToken(TokenType::kEqualEqual, start, GetPosition());
238
0
      }
239
0
      return MakeToken(TokenType::kEqual, start, GetPosition());
240
0
    }
241
0
    case '<': {
242
0
      Advance(1);
243
0
      if (Consume('=')) {
244
0
        return MakeToken(TokenType::kLessEqual, start, GetPosition());
245
0
      }
246
0
      return MakeToken(TokenType::kLess, start, GetPosition());
247
0
    }
248
0
    case '>': {
249
0
      Advance(1);
250
0
      if (Consume('=')) {
251
0
        return MakeToken(TokenType::kGreaterEqual, start, GetPosition());
252
0
      }
253
0
      return MakeToken(TokenType::kGreater, start, GetPosition());
254
0
    }
255
0
    case ':': {
256
0
      Advance(1);
257
0
      return MakeToken(TokenType::kColon, start, GetPosition());
258
0
    }
259
0
    case '%': {
260
0
      Advance(1);
261
0
      return MakeToken(TokenType::kPercent, start, GetPosition());
262
0
    }
263
0
    case '+': {
264
0
      Advance(1);
265
0
      return MakeToken(TokenType::kPlus, start, GetPosition());
266
0
    }
267
0
    case '-': {
268
0
      Advance(1);
269
0
      return MakeToken(TokenType::kMinus, start, GetPosition());
270
0
    }
271
0
    case '*': {
272
0
      Advance(1);
273
0
      return MakeToken(TokenType::kAsterisk, start, GetPosition());
274
0
    }
275
0
    case '/': {
276
0
      Advance(1);
277
0
      if (Consume('/')) {
278
0
        ConsumeLine();
279
0
        return MakeToken(TokenType::kComment, start, GetPosition());
280
0
      }
281
0
      return MakeToken(TokenType::kSlash, start, GetPosition());
282
0
    }
283
0
    case '&': {
284
0
      Advance(1);
285
0
      if (Consume('&')) {
286
0
        return MakeToken(TokenType::kLogicalAnd, start, GetPosition());
287
0
      }
288
0
      return SetError(start, GetPosition(),
289
0
                      "unexpected single '&', expected '&&'");
290
0
    }
291
0
    case '|': {
292
0
      Advance(1);
293
0
      if (Consume('|')) {
294
0
        return MakeToken(TokenType::kLogicalOr, start, GetPosition());
295
0
      }
296
0
      return SetError(start, GetPosition(),
297
0
                      "unexpected single '|', expected '||'");
298
0
    }
299
0
    case '_': {
300
0
      return ConsumeIdent();
301
0
    }
302
0
    case '`': {
303
0
      return ConsumeQuotedIdent();
304
0
    }
305
0
    case '\'': {
306
0
      return ConsumeStringLiteral(start, '\'');
307
0
    }
308
0
    case '"': {
309
0
      return ConsumeStringLiteral(start, '"');
310
0
    }
311
0
    case 'r':
312
0
      ABSL_FALLTHROUGH_INTENDED;
313
0
    case 'R':
314
0
      ABSL_FALLTHROUGH_INTENDED;
315
0
    case 'b':
316
0
      ABSL_FALLTHROUGH_INTENDED;
317
0
    case 'B': {
318
0
      if (auto token = ConsumePrefixedStringLiteral(); token.has_value()) {
319
0
        return *token;
320
0
      }
321
0
      break;
322
0
    }
323
0
    default:
324
0
      break;
325
0
  }
326
0
  if (c <= 0x7f && absl::ascii_isdigit(static_cast<char>(c))) {
327
0
    return ConsumeNumericLiteral();
328
0
  }
329
0
  if (c <= 0x7f && absl::ascii_isalpha(static_cast<char>(c))) {
330
    // Root identifiers (the ones starting with a period) are returned as
331
    // a sequence of kDot and kIdent tokens.
332
0
    return ConsumeIdent();
333
0
  }
334
0
  Advance(1);
335
0
  return SetError(start, GetPosition(), "unexpected character");
336
0
}
337
338
// Consumes characters up to and including the first occurrence of character `c`
339
// without interpreting backslashes as escapes.
340
// Returns true if `c` was found and consumed; false if end of input was
341
// reached.
342
0
bool Lexer::ConsumeUntilAfter(char32_t c) {
343
0
  ABSL_DCHECK_NE(c, '\n');
344
0
  for (int32_t pos = position_; pos < content_.size(); ++pos) {
345
0
    if (content_.at(pos) == c) {
346
0
      AdvanceProcessingNewLines(pos + 1);
347
0
      return true;
348
0
    }
349
0
  }
350
0
  AdvanceProcessingNewLines(content_.size());
351
0
  return false;
352
0
}
353
354
// Consumes characters up to and including the first occurrence of substring `s`
355
// without interpreting backslashes as escapes (`s` must not contain newlines).
356
// Returns true if `s` was found and consumed; false if end of input was
357
// reached.
358
0
bool Lexer::ConsumeUntilAfterString(std::u32string_view s) {
359
0
  ABSL_DCHECK(s.find(U'\n') == std::u32string_view::npos);
360
0
  int32_t pos = position_;
361
0
  while (pos + static_cast<int32_t>(s.size()) <= content_.size()) {
362
0
    bool match = true;
363
0
    for (size_t i = 0; i < s.size(); ++i) {
364
0
      if (content_.at(pos + static_cast<int32_t>(i)) != s[i]) {
365
0
        match = false;
366
0
        break;
367
0
      }
368
0
    }
369
0
    if (match) {
370
0
      AdvanceProcessingNewLines(pos + static_cast<int32_t>(s.size()));
371
0
      return true;
372
0
    }
373
0
    ++pos;
374
0
  }
375
0
  AdvanceProcessingNewLines(content_.size());
376
0
  return false;
377
0
}
378
379
// Consumes characters up to and including the first occurrence of `c` that is
380
// not preceded by an odd number of backslash ('\') escape characters. Returns
381
// true if an unescaped `c` was found and consumed; false if reached EOF.
382
0
bool Lexer::ConsumeUntilAfterUnescaped(char32_t c) {
383
0
  ABSL_DCHECK_NE(c, '\n');
384
0
  ABSL_DCHECK_NE(c, '\\');
385
0
  int32_t pos = position_;
386
0
  bool escaped = false;
387
0
  while (pos < content_.size()) {
388
0
    char32_t cc = content_.at(pos);
389
0
    if (cc == '\\') {
390
0
      escaped = !escaped;
391
0
    } else {
392
0
      if (cc == c && !escaped) {
393
0
        AdvanceProcessingNewLines(pos + 1);
394
0
        return true;
395
0
      }
396
0
      escaped = false;
397
0
    }
398
0
    ++pos;
399
0
  }
400
0
  AdvanceProcessingNewLines(content_.size());
401
0
  return false;
402
0
}
403
404
// Consumes characters up to and including the first occurrence of substring `s`
405
// where the first character of `s` is not preceded by an odd number of
406
// backslashes. Returns true if an unescaped `s` was found and consumed; false
407
// if reached EOF.
408
0
bool Lexer::ConsumeUntilAfterUnescapedString(std::u32string_view s) {
409
0
  ABSL_DCHECK(s.find(U'\n') == std::u32string_view::npos);
410
0
  int32_t pos = position_;
411
0
  bool escaped = false;
412
0
  while (pos < content_.size()) {
413
0
    char32_t cc = content_.at(pos);
414
0
    if (cc == '\\') {
415
0
      escaped = !escaped;
416
0
    } else {
417
0
      if (!escaped && pos + static_cast<int32_t>(s.size()) <= content_.size()) {
418
0
        bool match = true;
419
0
        for (size_t j = 0; j < s.size(); ++j) {
420
0
          if (content_.at(pos + static_cast<int32_t>(j)) != s[j]) {
421
0
            match = false;
422
0
            break;
423
0
          }
424
0
        }
425
0
        if (match) {
426
0
          AdvanceProcessingNewLines(pos + static_cast<int32_t>(s.size()));
427
0
          return true;
428
0
        }
429
0
      }
430
0
      escaped = false;
431
0
    }
432
0
    ++pos;
433
0
  }
434
0
  AdvanceProcessingNewLines(content_.size());
435
0
  return false;
436
0
}
437
438
0
bool Lexer::MatchString(std::u32string_view s) const {
439
0
  if (position_ + static_cast<int32_t>(s.size()) > content_.size()) {
440
0
    return false;
441
0
  }
442
0
  for (size_t i = 0; i < s.size(); ++i) {
443
0
    if (content_.at(position_ + static_cast<int32_t>(i)) != s[i]) {
444
0
      return false;
445
0
    }
446
0
  }
447
0
  return true;
448
0
}
449
450
std::optional<char32_t> Lexer::MatchIf(
451
0
    absl::FunctionRef<bool(char32_t)> predicate) const {
452
0
  if (position_ < content_.size()) {
453
0
    char32_t cp = content_.at(position_);
454
0
    if (predicate(cp)) {
455
0
      return cp;
456
0
    }
457
0
  }
458
0
  return std::nullopt;
459
0
}
460
461
0
void Lexer::ConsumeLine() {
462
0
  while (position_ < content_.size()) {
463
0
    if (content_.at(position_) == '\n') {
464
0
      Advance(1);
465
0
      return;
466
0
    }
467
0
    Advance(1);
468
0
  }
469
0
}
470
471
0
void Lexer::ConsumeWhitespace() {
472
0
  while (position_ < content_.size()) {
473
0
    char32_t c = content_.at(position_);
474
0
    switch (c) {
475
0
      case '\f':
476
0
        ABSL_FALLTHROUGH_INTENDED;
477
0
      case '\n':
478
0
        ABSL_FALLTHROUGH_INTENDED;
479
0
      case ' ':
480
0
        ABSL_FALLTHROUGH_INTENDED;
481
0
      case '\r':
482
0
        ABSL_FALLTHROUGH_INTENDED;
483
0
      case '\v':
484
0
        ABSL_FALLTHROUGH_INTENDED;
485
0
      case '\t':
486
0
        Advance(1);
487
0
        break;
488
0
      default:
489
0
        return;
490
0
    }
491
0
  }
492
0
}
493
494
0
bool Lexer::Consume(char32_t c) {
495
0
  ABSL_DCHECK_NE(c, '\n');
496
0
  if (Match(c)) {
497
0
    Advance(1);
498
0
    return true;
499
0
  }
500
0
  return false;
501
0
}
502
503
0
bool Lexer::ConsumeIgnoreCase(char32_t c) {
504
0
  ABSL_DCHECK_NE(c, '\n');
505
0
  if (MatchIgnoreCase(c)) {
506
0
    Advance(1);
507
0
    return true;
508
0
  }
509
0
  return false;
510
0
}
511
512
0
bool Lexer::ConsumeString(std::u32string_view s) {
513
0
  ABSL_DCHECK(s.find(U'\n') == std::u32string_view::npos);
514
0
  if (MatchString(s)) {
515
0
    Advance(s.size());
516
0
    return true;
517
0
  }
518
0
  return false;
519
0
}
520
521
std::optional<char32_t> Lexer::ConsumeIf(
522
    absl::FunctionRef<bool(char32_t)> predicate) {
523
  std::optional<char32_t> match = MatchIf(predicate);
524
  if (match.has_value()) {
525
    ABSL_DCHECK_NE(*match, '\n');
526
    Advance(1);
527
  }
528
  return match;
529
}
530
531
0
bool Lexer::ConsumeDigits() {
532
0
  bool advanced = false;
533
0
  while (position_ < content_.size()) {
534
0
    char32_t c = content_.at(position_);
535
0
    if (c > 0x7f || !absl::ascii_isdigit(static_cast<char>(c))) {
536
0
      break;
537
0
    }
538
0
    Advance(1);
539
0
    advanced = true;
540
0
  }
541
0
  return advanced;
542
0
}
543
544
0
bool Lexer::ConsumeHexDigits() {
545
0
  bool advanced = false;
546
0
  while (position_ < content_.size()) {
547
0
    char32_t c = content_.at(position_);
548
0
    if (c > 0x7f || !absl::ascii_isxdigit(static_cast<char>(c))) {
549
0
      break;
550
0
    }
551
0
    Advance(1);
552
0
    advanced = true;
553
0
  }
554
0
  return advanced;
555
0
}
556
557
0
TokenType Lexer::ConsumeIntegralSuffix() {
558
0
  if (ConsumeIgnoreCase('u')) {
559
0
    return TokenType::kUint;
560
0
  }
561
0
  return TokenType::kInt;
562
0
}
563
564
0
Token Lexer::ConsumeQuotedIdent() {
565
0
  int32_t start = GetPosition();
566
0
  Advance(1);
567
0
  if (!ConsumeUntilAfter('`')) {
568
0
    return SetError(start, GetPosition(), "unterminated quoted identifier");
569
0
  }
570
0
  return MakeToken(TokenType::kIdent, start, GetPosition());
571
0
}
572
573
Token Lexer::ConsumeStringLiteral(int32_t start, char32_t quote, bool is_bytes,
574
0
                                  bool is_raw) {
575
0
  Advance(1);
576
0
  std::u32string triple_quote(3, quote);
577
0
  if (ConsumeString(std::u32string_view(triple_quote.data(), 2))) {
578
0
    if (is_raw ? !ConsumeUntilAfterString(triple_quote)
579
0
               : !ConsumeUntilAfterUnescapedString(triple_quote)) {
580
0
      return SetError(start, GetPosition(),
581
0
                      is_bytes ? "unterminated bytes literal"
582
0
                               : "unterminated string literal");
583
0
    }
584
0
    return MakeToken(is_bytes ? TokenType::kBytes : TokenType::kString, start,
585
0
                     GetPosition());
586
0
  }
587
0
  if (is_raw ? !ConsumeUntilAfter(quote) : !ConsumeUntilAfterUnescaped(quote)) {
588
0
    return SetError(start, GetPosition(),
589
0
                    is_bytes ? "unterminated bytes literal"
590
0
                             : "unterminated string literal");
591
0
  }
592
0
  return MakeToken(is_bytes ? TokenType::kBytes : TokenType::kString, start,
593
0
                   GetPosition());
594
0
}
595
596
// Consumes prefixed string and bytes literals.
597
// Handles the following prefix sequences (case-insensitive for 'r' and 'b'):
598
// - Raw strings: r"...", r'...', r"""...""", r'''...'''
599
// - Bytes: b"...", b'...', b"""...""", b'''...'''
600
// - Raw bytes: br"...", br'...', br"""...""", br'''...''', rb"...", rb'...',
601
//              rb"""...""", rb'''...'''
602
0
std::optional<Token> Lexer::ConsumePrefixedStringLiteral() {
603
0
  int32_t start = GetPosition();
604
0
  if (position_ >= content_.size()) return std::nullopt;
605
0
  char32_t c = content_.at(position_);
606
0
  bool is_bytes = (c == 'b' || c == 'B');
607
0
  bool is_raw = (c == 'r' || c == 'R');
608
0
  size_t lookahead = 1;
609
0
  if (position_ + 1 < content_.size()) {
610
0
    char32_t c2 = content_.at(position_ + 1);
611
0
    if ((is_bytes && (c2 == 'r' || c2 == 'R')) ||
612
0
        (!is_bytes && (c2 == 'b' || c2 == 'B'))) {
613
0
      is_bytes = true;
614
0
      is_raw = true;
615
0
      lookahead = 2;
616
0
    }
617
0
  }
618
0
  if (position_ + static_cast<int32_t>(lookahead) < content_.size()) {
619
0
    char32_t quote = content_.at(position_ + static_cast<int32_t>(lookahead));
620
0
    if (quote == '"' || quote == '\'') {
621
0
      Advance(lookahead);
622
0
      return ConsumeStringLiteral(start, quote, is_bytes, is_raw);
623
0
    }
624
0
  }
625
0
  return std::nullopt;
626
0
}
627
628
// Consumes a numeric literal token and returns its TokenType (kInt, kUint, or
629
// kFloat). Recognizes the following literal formats:
630
// - Hexadecimal integers (kInt / kUint): 0x1A, 0XFFu, 0x0U
631
// - Decimal integers (kInt / kUint): 0, 45U, 123456
632
// - Floating-point numbers (kFloat): .12345, 1.23, 1e6, 1.5e+10, .5e-3
633
0
Token Lexer::ConsumeNumericLiteral() {
634
0
  int32_t start = GetPosition();
635
0
  char32_t c = content_.at(position_);
636
0
  bool floating_point = false;
637
0
  if (c == '.') {
638
0
    floating_point = true;
639
0
    Advance(1);
640
0
    if (!ConsumeDigits()) {
641
0
      return SetError(
642
0
          start, GetPosition(),
643
0
          "floating point literal missing digits after decimal separator");
644
0
    }
645
0
  } else {
646
0
    Advance(1);
647
0
    if (c == '0') {
648
0
      if (ConsumeIgnoreCase('x')) {
649
0
        if (!ConsumeHexDigits()) {
650
0
          return SetError(
651
0
              start, GetPosition(),
652
0
              "integral literal missing digits after hexadecimal separator");
653
0
        }
654
0
        auto token_type = ConsumeIntegralSuffix();
655
0
        if (ConsumeIf(IsIdentTrailing)) {
656
0
          return SetError(
657
0
              start, GetPosition(),
658
0
              absl::StrCat(TokenTypeToString(token_type),
659
0
                           " literal has unexpected trailing characters"));
660
0
        }
661
0
        return MakeToken(token_type, start, GetPosition());
662
0
      }
663
0
    }
664
0
    static_cast<void>(ConsumeDigits());
665
0
    if (position_ < content_.size() && content_.at(position_) == '.' &&
666
0
        position_ + 1 < content_.size() && content_.at(position_ + 1) <= 0x7f &&
667
0
        absl::ascii_isdigit(static_cast<char>(content_.at(position_ + 1)))) {
668
0
      floating_point = true;
669
0
      Advance(1);
670
0
      static_cast<void>(ConsumeDigits());
671
0
    }
672
0
  }
673
0
  if (ConsumeIgnoreCase('e')) {
674
0
    floating_point = true;
675
0
    static_cast<void>(ConsumeIf(IsPlusOrMinus));
676
0
    if (!ConsumeDigits()) {
677
0
      return SetError(
678
0
          start, GetPosition(),
679
0
          "floating point literal missing digits after exponent separator");
680
0
    }
681
0
  }
682
0
  auto token_type =
683
0
      floating_point ? TokenType::kFloat : ConsumeIntegralSuffix();
684
0
  if (ConsumeIf(IsIdentTrailing)) {
685
0
    return SetError(
686
0
        start, GetPosition(),
687
0
        absl::StrCat(TokenTypeToString(token_type),
688
0
                     " literal has unexpected trailing characters"));
689
0
  }
690
0
  return MakeToken(token_type, start, GetPosition());
691
0
}
692
693
0
Token Lexer::ConsumeIdent() {
694
0
  int32_t start = GetPosition();
695
0
  while (position_ < content_.size()) {
696
0
    char32_t c = content_.at(position_);
697
0
    if (!IsIdentTrailing(c)) {
698
0
      break;
699
0
    }
700
0
    Advance(1);
701
0
  }
702
0
  int32_t end = GetPosition();
703
0
  std::string word = content_.ToString(start, end);
704
0
  const auto& keywords = Keywords();
705
0
  if (auto it = keywords.find(word); it != keywords.end()) {
706
0
    return MakeToken(it->second, start, end);
707
0
  }
708
0
  return MakeToken(TokenType::kIdent, start, end);
709
0
}
710
711
}  // namespace cel::parser_internal