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.h
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
#ifndef THIRD_PARTY_CEL_CPP_PARSER_INTERNAL_LEXER_H_
16
#define THIRD_PARTY_CEL_CPP_PARSER_INTERNAL_LEXER_H_
17
18
#include <cstddef>
19
#include <cstdint>
20
#include <limits>
21
#include <optional>
22
#include <string>
23
#include <string_view>
24
#include <utility>
25
26
#include "absl/base/attributes.h"
27
#include "absl/base/optimization.h"
28
#include "absl/functional/function_ref.h"
29
#include "absl/log/absl_check.h"
30
#include "absl/strings/ascii.h"
31
#include "common/source.h"
32
33
namespace cel::parser_internal {
34
35
enum class TokenType {
36
  kError = 0,
37
  kEnd,
38
  kWhitespace,
39
  kComment,
40
41
  // Keywords
42
  kNull,
43
  kFalse,
44
  kTrue,
45
  kIn,
46
  kReservedWord,
47
48
  // Literals
49
  kInt,
50
  kUint,
51
  kFloat,
52
  kString,
53
  kBytes,
54
55
  // Identifiers (standard bare identifiers and backtick-quoted identifiers).
56
  // Note: The lexer does not validate whether a quoted/escaped identifier is
57
  // source-legal or permitted in its syntactic context. Because
58
  // non-source-legal identifiers are used internally in macros and functions,
59
  // the parser must strictly validate the characters inside quoted identifiers
60
  // and verify that they only appear where permitted (e.g., field selections
61
  // and struct field specifiers).
62
  kIdent,
63
64
  // Delimiters
65
  kLeftBracket,   // [
66
  kRightBracket,  // ]
67
  kLeftBrace,     // {
68
  kRightBrace,    // }
69
  kLeftParen,     // (
70
  kRightParen,    // )
71
72
  // Operators
73
  kDot,               // .
74
  kComma,             // ,
75
  kMinus,             // -
76
  kPlus,              // +
77
  kAsterisk,          // *
78
  kSlash,             // /
79
  kPercent,           // %
80
  kQuestion,          // ?
81
  kColon,             // :
82
  kExclamation,       // !
83
  kEqual,             // =
84
  kEqualEqual,        // ==
85
  kExclamationEqual,  // !=
86
  kLess,              // <
87
  kLessEqual,         // <=
88
  kGreater,           // >
89
  kGreaterEqual,      // >=
90
  kLogicalAnd,        // &&
91
  kLogicalOr,         // ||
92
};
93
94
ABSL_ATTRIBUTE_PURE_FUNCTION std::string_view TokenTypeToString(TokenType type);
95
96
struct Token final {
97
  TokenType type = TokenType::kError;
98
  int32_t start = 0;
99
  int32_t end = 0;
100
};
101
102
struct LexerError final {
103
  int32_t start = 0;
104
  int32_t end = 0;
105
  std::string message;
106
};
107
108
// Lexer performs fast tokenization of CEL expression source code.
109
//
110
// Responsibilities & Parser Expectations:
111
// This lexer is designed for speed and does not perform comprehensive semantic
112
// or syntax validation:
113
//
114
// 1. String and Bytes Literal Escape Sequences:
115
//    - For standard single- and double-quoted literals ("..." and '...'), the
116
//      lexer recognizes backslash ('\') only to determine whether the closing
117
//      delimiter ('"' or '\'') is escaped (e.g., \" and \' do not terminate the
118
//      literal, whereas \\" terminates it because the backslash is escaped).
119
//    - For triple-quoted literals ("""...""" and '''...''') and raw literals
120
//      (r"...", r'''...'''), backslashes and escape sequences are not processed
121
//      when locating the closing delimiter.
122
//    - The lexer does NOT validate, decode, or check the syntax of any escape
123
//      sequences (e.g., \n, \r, \t, \xHH, \uHHHH, \U00HHHHHH, \0, or invalid
124
//      escapes like \q). All characters and backslashes within the literal
125
//      boundaries are preserved verbatim in the token's text span.
126
//    - The parser/caller is strictly responsible for validating and unescaping
127
//      all escape sequences and reporting syntax errors for invalid escape
128
//      sequences when converting string and bytes tokens during AST
129
//      construction.
130
//
131
// 2. Numeric Literals:
132
//    - Performs only general bounds and format matching for integers and
133
//      floating-point numeric literals. The lexer expects the parser to perform
134
//      final validation and numeric conversion when building the AST.
135
class Lexer final {
136
 public:
137
  explicit Lexer(const cel::Source& source)
138
0
      : content_(source.content()), position_(0) {
139
0
    ABSL_DCHECK_LE(content_.size(), static_cast<SourcePosition>(
140
0
                                        std::numeric_limits<int32_t>::max()));
141
0
  }
142
143
  struct Position final {
144
    int32_t position = 0;
145
    bool at_end = false;
146
    bool done = false;
147
    LexerError error;
148
  };
149
150
  Lexer(const Lexer&) = delete;
151
  Lexer(Lexer&&) = delete;
152
  Lexer& operator=(const Lexer&) = delete;
153
  Lexer& operator=(Lexer&&) = delete;
154
155
  // Scans and returns the next token from the source.
156
  [[nodiscard]] ABSL_ATTRIBUTE_NOINLINE Token Lex();
157
158
  // Inspect the error from the last call to `Lex()` that returned an error
159
  // token. The reference is not guaranteed to be valid after further calls to
160
  // `Lex`.
161
  [[nodiscard]] const LexerError& GetError() const
162
0
      ABSL_ATTRIBUTE_LIFETIME_BOUND {
163
0
    return error_;
164
0
  }
165
166
0
  [[nodiscard]] int32_t GetPosition() const { return position_; }
167
168
0
  [[nodiscard]] Position SavePosition() const {
169
0
    return Position{position_, at_end_, done_, error_};
170
0
  }
171
172
0
  void RestorePosition(const Position& position) {
173
0
    position_ = position.position;
174
0
    at_end_ = position.at_end;
175
0
    done_ = position.done;
176
0
    error_ = position.error;
177
0
  }
178
179
 private:
180
0
  [[nodiscard]] bool Match(char32_t c) const {
181
0
    return position_ < content_.size() && content_.at(position_) == c;
182
0
  }
183
184
0
  [[nodiscard]] bool MatchIgnoreCase(char32_t c) const {
185
0
    if (position_ >= content_.size()) return false;
186
0
    char32_t cp = content_.at(position_);
187
0
    return cp <= 0x7f && c <= 0x7f &&
188
0
           absl::ascii_tolower(static_cast<char>(cp)) ==
189
0
               absl::ascii_tolower(static_cast<char>(c));
190
0
  }
191
192
0
  void Advance(size_t n) {
193
0
    ABSL_DCHECK_LE(n, static_cast<size_t>(content_.size() - position_));
194
0
    position_ += static_cast<int32_t>(n);
195
0
  }
196
197
0
  void AdvanceProcessingNewLines(int32_t end_position) {
198
0
    ABSL_DCHECK_LE(end_position, content_.size());
199
0
    ABSL_DCHECK_GE(end_position, position_);
200
0
    Advance(static_cast<size_t>(end_position - position_));
201
0
  }
202
203
0
  [[nodiscard]] Token MakeToken(TokenType type, int32_t start, int32_t end) {
204
0
    if (ABSL_PREDICT_FALSE(at_end_)) {
205
0
      AtEndTokenCreated();
206
0
    }
207
0
    return Token{.type = type, .start = start, .end = end};
208
0
  }
209
210
  [[nodiscard]] Token SetError(int32_t start, int32_t end,
211
0
                               std::string message) {
212
0
    error_ =
213
0
        LexerError{.start = start, .end = end, .message = std::move(message)};
214
0
    return Token{.type = TokenType::kError, .start = start, .end = end};
215
0
  }
216
217
0
  void AtEndTokenCreated() { done_ = true; }
218
219
  // Consumes characters up to and including the first occurrence of character
220
  // `c` without interpreting backslashes as escapes. Returns true if `c` was
221
  // found and consumed; false if end of input was reached.
222
  [[nodiscard]] bool ConsumeUntilAfter(char32_t c);
223
224
  // Consumes characters up to and including the first occurrence of substring
225
  // `s` without interpreting backslashes as escapes (`s` must not contain
226
  // newlines). Returns true if `s` was found and consumed; false if end of
227
  // input was reached.
228
  [[nodiscard]] bool ConsumeUntilAfterString(std::u32string_view s);
229
230
  // Consumes characters up to and including the first occurrence of `c` that is
231
  // not preceded by an odd number of backslash ('\') escape characters. Returns
232
  // true if an unescaped `c` was found and consumed; false if reached EOF.
233
  [[nodiscard]] bool ConsumeUntilAfterUnescaped(char32_t c);
234
235
  // Consumes characters up to and including the first occurrence of substring
236
  // `s` where the first character of `s` is not preceded by an odd number of
237
  // backslashes. Returns true if an unescaped `s` was found and consumed; false
238
  // if reached EOF.
239
  [[nodiscard]] bool ConsumeUntilAfterUnescapedString(std::u32string_view s);
240
241
  [[nodiscard]] bool MatchString(std::u32string_view s) const;
242
243
  [[nodiscard]] std::optional<char32_t> MatchIf(
244
      absl::FunctionRef<bool(char32_t)> predicate) const;
245
246
  void ConsumeLine();
247
248
  void ConsumeWhitespace();
249
250
  [[nodiscard]] bool Consume(char32_t c);
251
252
  [[nodiscard]] bool ConsumeIgnoreCase(char32_t c);
253
254
  [[nodiscard]] bool ConsumeString(std::u32string_view s);
255
256
  [[nodiscard]] std::optional<char32_t> ConsumeIf(
257
      absl::FunctionRef<bool(char32_t)> predicate);
258
259
  [[nodiscard]] bool ConsumeDigits();
260
261
  [[nodiscard]] bool ConsumeHexDigits();
262
263
  [[nodiscard]] TokenType ConsumeIntegralSuffix();
264
265
  // Consumes a backtick-quoted identifier (`...`) and returns
266
  // TokenType::kIdent. The token text preserves the surrounding backticks so
267
  // the parser can detect quoted identifiers and enforce restrictions on their
268
  // characters and allowed syntactic locations (such as field selections and
269
  // struct field specifiers).
270
  [[nodiscard]] Token ConsumeQuotedIdent();
271
272
  [[nodiscard]] Token ConsumeStringLiteral(int32_t start, char32_t quote,
273
                                           bool is_bytes = false,
274
                                           bool is_raw = false);
275
276
  // Consumes prefixed string and bytes literals.
277
  // Handles the following prefix sequences (case-insensitive for 'r' and 'b'):
278
  // - Raw strings: r"...", r'...', r"""...""", r'''...'''
279
  // - Bytes: b"...", b'...', b"""...""", b'''...'''
280
  // - Raw bytes: br"...", br'...', br"""...""", br'''...''', rb"...", rb'...',
281
  // rb"""...""", rb'''...'''
282
  [[nodiscard]] std::optional<Token> ConsumePrefixedStringLiteral();
283
284
  // Consumes a numeric literal token and returns its TokenType (kInt, kUint, or
285
  // kFloat). Recognizes the following literal formats:
286
  // - Decimal integers (kInt / kUint): 0, 45U, 123456
287
  // - Hexadecimal integers (kInt / kUint): 0x1A, 0XFFu, 0x0U
288
  // - Floating-point numbers (kFloat): .12345, 1.23, 1e6, 1.5e+10, .5e-3
289
  [[nodiscard]] Token ConsumeNumericLiteral();
290
291
  // Consumes an identifier token and checks if it matches any reserved
292
  // keywords.
293
  [[nodiscard]] Token ConsumeIdent();
294
295
  cel::SourceContentView content_;
296
  int32_t position_ = 0;
297
  bool at_end_ = false;
298
  bool done_ = false;
299
  LexerError error_;
300
};
301
302
}  // namespace cel::parser_internal
303
304
#endif  // THIRD_PARTY_CEL_CPP_PARSER_INTERNAL_LEXER_H_