Coverage Report

Created: 2026-10-09 06:57

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()),
139
0
        content_size_(static_cast<int32_t>(content_.size())),
140
0
        position_(0) {
141
0
    ABSL_DCHECK_LE(content_.size(), static_cast<SourcePosition>(
142
0
                                        std::numeric_limits<int32_t>::max()));
143
0
  }
144
145
  Lexer(const Lexer&) = delete;
146
  Lexer(Lexer&&) = delete;
147
  Lexer& operator=(const Lexer&) = delete;
148
  Lexer& operator=(Lexer&&) = delete;
149
150
  // Scans and returns the next token from the source.
151
  [[nodiscard]] ABSL_ATTRIBUTE_NOINLINE Token Lex();
152
153
  // Inspect the error from the last call to `Lex()` that returned an error
154
  // token. The reference is not guaranteed to be valid after further calls to
155
  // `Lex`.
156
  [[nodiscard]] const LexerError& GetError() const
157
0
      ABSL_ATTRIBUTE_LIFETIME_BOUND {
158
0
    return error_;
159
0
  }
160
161
0
  [[nodiscard]] int32_t GetPosition() const { return position_; }
162
163
0
  [[nodiscard]] int32_t SavePosition() const { return position_; }
164
165
0
  void RestorePosition(int32_t position) {
166
0
    ABSL_DCHECK_GE(position, 0);
167
0
    ABSL_DCHECK_LE(position, content_size_);
168
0
    position_ = position;
169
0
    error_ = LexerError{};
170
0
  }
171
172
 private:
173
0
  [[nodiscard]] bool Match(char32_t c) const {
174
0
    return position_ < content_size_ && content_.at(position_) == c;
175
0
  }
176
177
0
  [[nodiscard]] bool MatchIgnoreCase(char32_t c) const {
178
0
    if (position_ >= content_size_) return false;
179
0
    char32_t cp = content_.at(position_);
180
0
    return cp <= 0x7f && c <= 0x7f &&
181
0
           absl::ascii_tolower(static_cast<char>(cp)) ==
182
0
               absl::ascii_tolower(static_cast<char>(c));
183
0
  }
184
185
0
  void Advance(size_t n) {
186
0
    ABSL_DCHECK_LE(n, static_cast<size_t>(content_size_ - position_));
187
0
    position_ += static_cast<int32_t>(n);
188
0
  }
189
190
0
  void AdvanceProcessingNewLines(int32_t end_position) {
191
0
    ABSL_DCHECK_LE(end_position, content_size_);
192
0
    ABSL_DCHECK_GE(end_position, position_);
193
0
    Advance(static_cast<size_t>(end_position - position_));
194
0
  }
195
196
0
  [[nodiscard]] Token MakeToken(TokenType type, int32_t start, int32_t end) {
197
0
    return Token{.type = type, .start = start, .end = end};
198
0
  }
199
200
  [[nodiscard]] Token SetError(int32_t start, int32_t end,
201
0
                               std::string message) {
202
0
    error_ =
203
0
        LexerError{.start = start, .end = end, .message = std::move(message)};
204
0
    return Token{.type = TokenType::kError, .start = start, .end = end};
205
0
  }
206
207
  // Consumes characters up to and including the first occurrence of character
208
  // `c`. If `is_raw` is false, backslashes are interpreted as escapes. Returns
209
  // true if `c` was found and consumed; false if end of input or an unescaped
210
  // newline was reached.
211
  [[nodiscard]] bool ConsumeUntilAfter(char32_t c, bool is_raw);
212
213
  // Consumes characters up to and including the first occurrence of substring
214
  // `s` without interpreting backslashes as escapes (`s` must not contain
215
  // newlines). Returns true if `s` was found and consumed; false if end of
216
  // input was reached.
217
  [[nodiscard]] bool ConsumeUntilAfterString(std::u32string_view s);
218
219
  // Consumes characters up to and including the first occurrence of substring
220
  // `s` where the first character of `s` is not preceded by an odd number of
221
  // backslashes. Returns true if an unescaped `s` was found and consumed; false
222
  // if reached EOF.
223
  [[nodiscard]] bool ConsumeUntilAfterUnescapedString(std::u32string_view s);
224
225
  [[nodiscard]] bool MatchString(std::u32string_view s) const;
226
227
  [[nodiscard]] std::optional<char32_t> MatchIf(
228
      absl::FunctionRef<bool(char32_t)> predicate) const;
229
230
  void ConsumeLine();
231
232
  void ConsumeWhitespace();
233
234
  [[nodiscard]] bool Consume(char32_t c);
235
236
  [[nodiscard]] bool ConsumeIgnoreCase(char32_t c);
237
238
  [[nodiscard]] bool ConsumeString(std::u32string_view s);
239
240
  [[nodiscard]] std::optional<char32_t> ConsumeIf(
241
      absl::FunctionRef<bool(char32_t)> predicate);
242
243
  [[nodiscard]] bool ConsumeDigits();
244
245
  [[nodiscard]] bool ConsumeHexDigits();
246
247
  [[nodiscard]] TokenType ConsumeIntegralSuffix();
248
249
  // Consumes a backtick-quoted identifier (`...`) and returns
250
  // TokenType::kIdent. The token text preserves the surrounding backticks so
251
  // the parser can detect quoted identifiers and enforce restrictions on their
252
  // characters and allowed syntactic locations (such as field selections and
253
  // struct field specifiers).
254
  [[nodiscard]] Token ConsumeQuotedIdent();
255
256
  [[nodiscard]] Token ConsumeStringLiteral(int32_t start, char32_t quote,
257
                                           bool is_bytes = false,
258
                                           bool is_raw = false);
259
260
  // Consumes prefixed string and bytes literals.
261
  // Handles the following prefix sequences (case-insensitive for 'r' and 'b'):
262
  // - Raw strings: r"...", r'...', r"""...""", r'''...'''
263
  // - Bytes: b"...", b'...', b"""...""", b'''...'''
264
  // - Raw bytes: br"...", br'...', br"""...""", br'''...''', rb"...", rb'...',
265
  // rb"""...""", rb'''...'''
266
  [[nodiscard]] std::optional<Token> ConsumePrefixedStringLiteral();
267
268
  // Consumes a numeric literal token and returns its TokenType (kInt, kUint, or
269
  // kFloat). Recognizes the following literal formats:
270
  // - Decimal integers (kInt / kUint): 0, 45U, 123456
271
  // - Hexadecimal integers (kInt / kUint): 0x1A, 0XFFu, 0x0U
272
  // - Floating-point numbers (kFloat): .12345, 1.23, 1e6, 1.5e+10, .5e-3
273
  [[nodiscard]] Token ConsumeNumericLiteral();
274
275
  // Consumes an identifier token and checks if it matches any reserved
276
  // keywords.
277
  [[nodiscard]] Token ConsumeIdent();
278
279
  cel::SourceContentView content_;
280
  int32_t content_size_ = 0;
281
  int32_t position_ = 0;
282
  LexerError error_;
283
};
284
285
}  // namespace cel::parser_internal
286
287
#endif  // THIRD_PARTY_CEL_CPP_PARSER_INTERNAL_LEXER_H_