Coverage Report

Created: 2026-10-31 06:21

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/cpython/Parser/lexer/lexer.c
Line
Count
Source
1
#include "Python.h"
2
#include "pycore_token.h"
3
#include "pycore_unicodeobject.h"
4
#include "errcode.h"
5
6
#include "lexer_internal.h"
7
#include "../tokenizer/helpers.h"
8
#include "../tokenizer/reader.h"
9
10
1.64M
#define MAKE_TOKEN(token_type) _PyLexer_token_setup(tok, token, token_type, p_start, p_end)
11
12
/* Spaces in this constant are treated as "zero or more spaces or tabs" when
13
   tokenizing. */
14
static const char* type_comment_prefix = "# type: ";
15
16
int
17
_PyLexer_refill(struct tok_state *tok)
18
484k
{
19
484k
    if (tok->done != E_OK) {
20
141k
        return 0;
21
141k
    }
22
343k
    int rc = _PyTok_ReaderUnderflow(tok);
23
#if defined(Py_DEBUG)
24
    if (tok->debug) {
25
        fprintf(stderr, "line[%d] = ", tok->lineno);
26
        _PyTokenizer_print_escape(
27
            stderr, _PyTok_SourcePointer(&tok->source, tok->cur),
28
            tok->inp - tok->cur);
29
        fprintf(stderr, "  tok->done = %d\n", tok->done);
30
    }
31
#endif
32
343k
    if (!rc) {
33
70.7k
        tok->cur = tok->inp;
34
70.7k
        return 0;
35
70.7k
    }
36
272k
    tok->line_start = tok->cur;
37
272k
    _PyTok_Span line = {tok->line_start, tok->inp};
38
272k
    if (_PyTok_SourceFindByte(&tok->source, line, '\0') >= 0) {
39
88
        _PyTokenizer_syntaxerror(tok, "source code cannot contain null bytes");
40
88
        tok->cur = tok->inp;
41
88
        return 0;
42
88
    }
43
272k
    return 1;
44
272k
}
45
46
/* Back-up one character */
47
void
48
_PyLexer_backup(struct tok_state *tok, int c)
49
4.25M
{
50
4.25M
    if (c != EOF) {
51
4.11M
        if (--tok->cur < tok->buf_offset) {
52
0
            Py_FatalError("tokenizer beginning of buffer");
53
0
        }
54
4.11M
        if (_PyTok_SourceByte(&tok->source, tok->cur) != Py_CHARMASK(c)) {
55
0
            Py_FatalError("tok_backup: wrong character");
56
0
        }
57
4.11M
    }
58
4.25M
}
59
60
61
62
/* Verify that the identifier follows PEP 3131. */
63
static int
64
verify_identifier(struct tok_state *tok)
65
12.9k
{
66
12.9k
    if (tok->tok_extra_tokens) {
67
0
        return 1;
68
0
    }
69
12.9k
    PyObject *s;
70
12.9k
    if (tok_failed(tok))
71
0
        return 0;
72
12.9k
    s = PyUnicode_DecodeUTF8(_PyTok_SourcePointer(&tok->source, tok->start),
73
12.9k
                             tok->cur - tok->start, NULL);
74
12.9k
    if (s == NULL) {
75
0
        if (PyErr_ExceptionMatches(PyExc_UnicodeDecodeError)) {
76
0
            tok->done = E_DECODE;
77
0
        }
78
0
        else {
79
0
            tok->done = E_ERROR;
80
0
        }
81
0
        return 0;
82
0
    }
83
12.9k
    Py_ssize_t invalid = _PyUnicode_ScanIdentifier(s);
84
12.9k
    assert(invalid >= 0);
85
12.9k
    assert(PyUnicode_GET_LENGTH(s) > 0);
86
12.9k
    if (invalid < PyUnicode_GET_LENGTH(s)) {
87
582
        Py_UCS4 ch = PyUnicode_READ_CHAR(s, invalid);
88
582
        _PyTok_Off error_cursor = tok->cur;
89
582
        if (invalid + 1 < PyUnicode_GET_LENGTH(s)) {
90
            /* Determine the offset in UTF-8 encoded input */
91
402
            Py_SETREF(s, PyUnicode_Substring(s, 0, invalid + 1));
92
402
            if (s != NULL) {
93
402
                Py_SETREF(s, PyUnicode_AsUTF8String(s));
94
402
            }
95
402
            if (s == NULL) {
96
0
                tok->done = E_ERROR;
97
0
                return 0;
98
0
            }
99
402
            error_cursor = tok->start + PyBytes_GET_SIZE(s);
100
402
        }
101
582
        Py_DECREF(s);
102
582
        if (Py_UNICODE_ISPRINTABLE(ch)) {
103
330
            _PyTokenizer_syntaxerror_at(
104
330
                tok, _PyTok_SourcePointer(&tok->source, tok->line_start),
105
330
                error_cursor - tok->line_start, tok->lineno, -1, -1,
106
330
                "invalid character '%c' (U+%04X)", ch, ch);
107
330
        }
108
252
        else {
109
252
            _PyTokenizer_syntaxerror_at(
110
252
                tok, _PyTok_SourcePointer(&tok->source, tok->line_start),
111
252
                error_cursor - tok->line_start, tok->lineno, -1, -1,
112
252
                "invalid non-printable character U+%04X", ch);
113
252
        }
114
582
        return 0;
115
582
    }
116
12.3k
    Py_DECREF(s);
117
12.3k
    return 1;
118
12.9k
}
119
120
int
121
_PyLexer_get_normal(struct tok_state *tok, ftstring_state *current, struct token *token)
122
1.96M
{
123
1.96M
    assert(current == NULL ||
124
1.96M
           (current->mode == FTSTRING_MODE_EXPRESSION &&
125
1.96M
            current->replacement_depth > 0));
126
1.96M
    int c;
127
1.96M
    int blankline, nonascii;
128
129
1.96M
    _PyTok_Off p_start = -1;
130
1.96M
    _PyTok_Off p_end = -1;
131
2.08M
  nextline:
132
2.08M
    tok->start = -1;
133
2.08M
    tok->start_loc = (_PyTok_Loc){tok->lineno, -1};
134
2.08M
    blankline = 0;
135
136
137
2.08M
    if (tok->layout.at_bol) {
138
329k
        blankline = _PyLexer_BeginLine(tok);
139
329k
        if (blankline < 0) {
140
133
            return MAKE_TOKEN(ERRORTOKEN);
141
133
        }
142
329k
    }
143
144
2.08M
    tok->start = tok->cur;
145
2.08M
    tok->start_loc = (_PyTok_Loc){
146
2.08M
        tok->lineno, tok->line_start >= 0 ? _PyLexer_ByteColumn(tok) : -1};
147
148
2.08M
    if (tok->layout.pending != 0) {
149
18.7k
        return _PyLexer_IndentationToken(tok, token);
150
18.7k
    }
151
152
    /* Peek ahead at the next character */
153
2.06M
    c = tok_nextc(tok);
154
2.06M
    tok_backup(tok, c);
155
156
2.06M
 again:
157
2.06M
    tok->start = -1;
158
    /* Skip spaces */
159
2.45M
    do {
160
2.45M
        c = tok_nextc(tok);
161
2.45M
    } while (c == ' ' || c == '\t' || c == '\014');
162
163
    /* Set start of current token */
164
2.06M
    tok->start = tok->cur - 1;
165
2.06M
    tok->start_loc = (_PyTok_Loc){
166
2.06M
        tok->lineno, tok->line_start >= 0 ? _PyLexer_ByteColumn(tok) - 1 : -1};
167
168
    /* Skip comment, unless it's a type comment */
169
2.06M
    if (c == '#') {
170
171
1.10M
        while (c != EOF && c != '\n' && c != '\r') {
172
1.06M
            c = tok_nextc(tok);
173
1.06M
        }
174
175
36.5k
        if (current != NULL) {
176
1.53k
            _PyTok_Off comment_end = tok->cur;
177
1.53k
            if (c == '\n' || c == '\r') {
178
1.53k
                comment_end--;
179
1.53k
            }
180
1.53k
            if (_PyLexer_record_ftstring_comment(
181
1.53k
                    tok, current, tok->start, comment_end) < 0) {
182
0
                tok->done = E_NOMEM;
183
0
                return MAKE_TOKEN(ERRORTOKEN);
184
0
            }
185
1.53k
        }
186
187
36.5k
        _PyTok_Off comment = tok->start;
188
36.5k
        if (tok->type_comments) {
189
0
            const char *prefix = type_comment_prefix;
190
0
            while (*prefix && comment < tok->cur) {
191
0
                if (*prefix == ' ') {
192
0
                    while (comment < tok->cur) {
193
0
                        int ch = _PyTok_SourceByte(&tok->source, comment);
194
0
                        if (ch != ' ' && ch != '\t') {
195
0
                            break;
196
0
                        }
197
0
                        comment++;
198
0
                    }
199
0
                } else if (*prefix == _PyTok_SourceByte(&tok->source, comment)) {
200
0
                    comment++;
201
0
                } else {
202
0
                    break;
203
0
                }
204
205
0
                prefix++;
206
0
            }
207
208
            /* This is a type comment if we matched all of type_comment_prefix. */
209
0
            if (!*prefix) {
210
0
                tok_backup(tok, c);  /* don't eat the newline or EOF */
211
                /* A TYPE_IGNORE is "type: ignore" followed by the end of the token
212
                 * or anything ASCII and non-alphanumeric. */
213
0
                int is_type_ignore = tok->cur - comment >= 6 &&
214
0
                    memcmp(_PyTok_SourcePointer(&tok->source, comment),
215
0
                           "ignore", 6) == 0;
216
0
                if (is_type_ignore && comment + 6 < tok->cur) {
217
0
                    int ch = _PyTok_SourceByte(&tok->source, comment + 6);
218
0
                    is_type_ignore = ch < 128 && !Py_ISALNUM(ch);
219
0
                }
220
221
0
                int type = is_type_ignore ? TYPE_IGNORE : TYPE_COMMENT;
222
0
                p_start = comment + (is_type_ignore ? 6 : 0);
223
0
                p_end = tok->cur;
224
0
                int start_col_offset = tok->start_loc.byte_col +
225
0
                    (int)(p_start - tok->start);
226
                /* Consume the newline after a standalone type ignore. */
227
0
                if (is_type_ignore && blankline) {
228
0
                    tok_nextc(tok);
229
0
                    tok->layout.at_bol = 1;
230
0
                }
231
0
                _PyLexer_token_setup(tok, token, type, p_start, p_end);
232
0
                token->start_loc = (_PyTok_Loc){tok->lineno, start_col_offset};
233
0
                token->end_loc = (_PyTok_Loc){tok->lineno,
234
0
                                              _PyLexer_ByteColumn(tok)};
235
0
                return type;
236
0
            }
237
0
        }
238
36.5k
        if (tok->implicit_newline == _PyTokenizer_IMPLY_NEWLINE &&
239
1.61k
            tok->layout.depth > 0) {
240
            // Ending this comment lets the next line dedent the suite.
241
51
            tok->needs_newline |= _PyTokenizer_NEWLINE_STATEMENT;
242
51
        }
243
36.5k
        if (tok->tok_extra_tokens) {
244
44
            tok_backup(tok, c);  /* don't eat the newline or EOF */
245
44
            p_start = comment;
246
44
            p_end = tok->cur;
247
44
            tok->layout.comment_newline = blankline;
248
44
            return MAKE_TOKEN(COMMENT);
249
44
        }
250
36.5k
    }
251
252
2.06M
    if (tok->done == E_INTERACT_STOP) {
253
0
        return MAKE_TOKEN(ENDMARKER);
254
0
    }
255
256
    /* Check for EOF and errors now */
257
2.06M
    if (c == EOF) {
258
70.6k
        if (tok->level) {
259
4.21k
            return MAKE_TOKEN(ERRORTOKEN);
260
4.21k
        }
261
66.4k
        return MAKE_TOKEN(tok->done == E_EOF ? ENDMARKER : ERRORTOKEN);
262
70.6k
    }
263
264
    /* Identifier (most frequent token!) */
265
1.99M
    nonascii = 0;
266
1.99M
    if (is_potential_identifier_start(c)) {
267
        /* Process the various legal combinations of b"", r"", u"", and f"". */
268
652k
        int saw_b = 0, saw_r = 0, saw_u = 0, saw_f = 0, saw_t = 0;
269
812k
        while (1) {
270
812k
            if (!saw_b && (c == 'b' || c == 'B')) {
271
17.2k
                saw_b = 1;
272
17.2k
            }
273
            /* Since this is a backwards compatibility support literal we don't
274
               want to support it in arbitrary order like byte literals. */
275
795k
            else if (!saw_u && (c == 'u'|| c == 'U')) {
276
59.4k
                saw_u = 1;
277
59.4k
            }
278
            /* ur"" and ru"" are not supported */
279
735k
            else if (!saw_r && (c == 'r' || c == 'R')) {
280
28.0k
                saw_r = 1;
281
28.0k
            }
282
707k
            else if (!saw_f && (c == 'f' || c == 'F')) {
283
47.3k
                saw_f = 1;
284
47.3k
            }
285
660k
            else if (!saw_t && (c == 't' || c == 'T')) {
286
34.5k
                saw_t = 1;
287
34.5k
            }
288
625k
            else {
289
625k
                break;
290
625k
            }
291
186k
            c = tok_nextc(tok);
292
186k
            if (c == '"' || c == '\'') {
293
                // Raise error on incompatible string prefixes:
294
26.6k
                int status = _PyLexer_check_string_prefixes(
295
26.6k
                    tok, saw_b, saw_r, saw_u, saw_f, saw_t);
296
26.6k
                if (status < 0) {
297
8
                    return MAKE_TOKEN(ERRORTOKEN);
298
8
                }
299
300
                // Handle valid f or t string creation:
301
26.6k
                if (saw_f || saw_t) {
302
21.7k
                    ftstring_kind kind = saw_t
303
21.7k
                        ? (saw_r ? RAW_TSTRING : TSTRING)
304
21.7k
                        : (saw_r ? RAW_FSTRING : FSTRING);
305
21.7k
                    return _PyLexer_scan_fstring_start(tok, token, c, kind);
306
21.7k
                }
307
4.93k
                return _PyLexer_scan_string(tok, token, c);
308
26.6k
            }
309
186k
        }
310
2.75M
        while (is_potential_identifier_char(c)) {
311
2.12M
            if (c >= 128) {
312
224k
                nonascii = 1;
313
224k
            }
314
2.12M
            c = tok_nextc(tok);
315
2.12M
        }
316
625k
        tok_backup(tok, c);
317
625k
        if (nonascii && !verify_identifier(tok)) {
318
582
            return MAKE_TOKEN(ERRORTOKEN);
319
582
        }
320
321
625k
        p_start = tok->start;
322
625k
        p_end = tok->cur;
323
324
625k
        return MAKE_TOKEN(NAME);
325
625k
    }
326
327
1.34M
    if (c == '\r') {
328
0
        c = tok_nextc(tok);
329
0
    }
330
331
1.34M
    if (c == '\n') {
332
256k
        int type = _PyLexer_Newline(tok, token, blankline);
333
256k
        if (type == 0) {
334
118k
            goto nextline;
335
118k
        }
336
137k
        return type;
337
256k
    }
338
339
    /* Period or number starting with period? */
340
1.08M
    if (c == '.') {
341
34.2k
        c = tok_nextc(tok);
342
34.2k
        if (Py_ISDIGIT(c)) {
343
4.18k
            return _PyLexer_scan_number(tok, token, c, 1);
344
30.0k
        } else if (c == '.') {
345
5.60k
            c = tok_nextc(tok);
346
5.60k
            if (c == '.') {
347
3.65k
                p_start = tok->start;
348
3.65k
                p_end = tok->cur;
349
3.65k
                return MAKE_TOKEN(ELLIPSIS);
350
3.65k
            }
351
1.94k
            else {
352
1.94k
                tok_backup(tok, c);
353
1.94k
            }
354
1.94k
            tok_backup(tok, '.');
355
1.94k
        }
356
24.4k
        else {
357
24.4k
            tok_backup(tok, c);
358
24.4k
        }
359
26.3k
        p_start = tok->start;
360
26.3k
        p_end = tok->cur;
361
26.3k
        return MAKE_TOKEN(DOT);
362
34.2k
    }
363
364
365
    /* Number */
366
1.05M
    if (Py_ISDIGIT(c)) {
367
103k
        return _PyLexer_scan_number(tok, token, c, 0);
368
103k
    }
369
370
    /* String */
371
951k
    if (c == '\'' || c == '"') {
372
34.3k
        return _PyLexer_scan_string(tok, token, c);
373
34.3k
    }
374
375
    /* Line continuation */
376
917k
    if (c == '\\') {
377
371
        if ((c = _PyLexer_ContinueLine(tok)) == -1) {
378
88
            return MAKE_TOKEN(ERRORTOKEN);
379
88
        }
380
283
        goto again; /* Read next line */
381
371
    }
382
383
    /* Punctuation character */
384
917k
    int is_punctuation = (c == ':' || c == '}' || c == '!');
385
917k
    if (is_punctuation && current != NULL) {
386
33.5k
        int type = _PyLexer_ftstring_punctuation(tok, current, token, c);
387
33.5k
        if (type < 0) {
388
0
            return MAKE_TOKEN(ERRORTOKEN);
389
0
        }
390
33.5k
        if (type != 0) {
391
5.26k
            p_start = tok->start;
392
5.26k
            p_end = tok->cur;
393
5.26k
            return MAKE_TOKEN(type);
394
5.26k
        }
395
33.5k
    }
396
397
    /* Check for two-character token */
398
911k
    {
399
911k
        int c2 = tok_nextc(tok);
400
911k
        int current_token = _PyToken_TwoChars(c, c2, tok->barry_as_bdfl);
401
911k
        if (current_token != OP) {
402
28.4k
            int c3 = tok_nextc(tok);
403
28.4k
            int current_token3 = _PyToken_ThreeChars(c, c2, c3);
404
28.4k
            if (current_token3 != OP) {
405
1.31k
                current_token = current_token3;
406
1.31k
            }
407
27.1k
            else {
408
27.1k
                tok_backup(tok, c3);
409
27.1k
            }
410
28.4k
            p_start = tok->start;
411
28.4k
            p_end = tok->cur;
412
28.4k
            return MAKE_TOKEN(current_token);
413
28.4k
        }
414
883k
        tok_backup(tok, c2);
415
883k
    }
416
417
    /* Keep track of parentheses nesting level */
418
0
    switch (c) {
419
67.9k
    case '(':
420
104k
    case '[':
421
160k
    case '{':
422
160k
        if (tok->level >= MAXLEVEL) {
423
15
            return MAKE_TOKEN(_PyTokenizer_syntaxerror(tok, "too many nested parentheses"));
424
15
        }
425
160k
        tok->parenstack[tok->level] = c;
426
160k
        tok->parenlinenostack[tok->level] = tok->lineno;
427
160k
        tok->parencolstack[tok->level] = (int)(tok->start - tok->line_start);
428
160k
        tok->level++;
429
160k
        break;
430
38.3k
    case ')':
431
45.2k
    case ']':
432
75.7k
    case '}':
433
75.7k
        if (current != NULL &&
434
26.8k
                _PyLexer_FTStringBracketDepth(tok, current) == 0) {
435
0
            if (c == '}') {
436
0
                return MAKE_TOKEN(_PyTokenizer_syntaxerror(tok,
437
0
                    "%c-string: single '}' is not allowed",
438
0
                    _PyLexer_StringPrefix(current->kind)));
439
0
            }
440
0
            return MAKE_TOKEN(_PyTokenizer_syntaxerror(
441
0
                tok, "%c-string: unmatched '%c'",
442
0
                _PyLexer_StringPrefix(current->kind), c));
443
0
        }
444
75.7k
        if (!tok->tok_extra_tokens && !tok->level) {
445
219
            return MAKE_TOKEN(_PyTokenizer_syntaxerror(tok, "unmatched '%c'", c));
446
219
        }
447
75.5k
        if (tok->level > 0) {
448
75.5k
            tok->level--;
449
75.5k
            int opening = tok->parenstack[tok->level];
450
75.5k
            if (!((opening == '(' && c == ')') ||
451
37.3k
                  (opening == '[' && c == ']') ||
452
30.5k
                  (opening == '{' && c == '}'))) {
453
                /* Do not match a closer against the brace that opened the
454
                 * current replacement field.  The f-string state relies on
455
                 * that brace, so this is checked even with extra tokens. */
456
67
                if (current != NULL && opening == '{') {
457
10
                    int bracket_depth =
458
10
                        _PyLexer_FTStringBracketDepth(tok, current);
459
10
                    if (bracket_depth == current->replacement_depth - 1) {
460
6
                        return MAKE_TOKEN(_PyTokenizer_syntaxerror(tok,
461
6
                            "%c-string: unmatched '%c'",
462
6
                            _PyLexer_StringPrefix(current->kind), c));
463
6
                    }
464
10
                }
465
61
                if (tok->tok_extra_tokens) {
466
                    /* Allow other mismatched brackets. */
467
0
                }
468
61
                else if (tok->parenlinenostack[tok->level] != tok->lineno) {
469
3
                    return MAKE_TOKEN(_PyTokenizer_syntaxerror(tok,
470
3
                            "closing parenthesis '%c' does not match "
471
3
                            "opening parenthesis '%c' on line %d",
472
3
                            c, opening, tok->parenlinenostack[tok->level]));
473
3
                }
474
58
                else {
475
58
                    return MAKE_TOKEN(_PyTokenizer_syntaxerror(tok,
476
58
                            "closing parenthesis '%c' does not match "
477
58
                            "opening parenthesis '%c'",
478
58
                            c, opening));
479
58
                }
480
61
            }
481
75.5k
        }
482
75.4k
        if (current != NULL &&
483
26.8k
                _PyLexer_close_ftstring_expr(tok, current, c) < 0) {
484
0
            return MAKE_TOKEN(ERRORTOKEN);
485
0
        }
486
75.4k
        break;
487
647k
    default:
488
647k
        break;
489
883k
    }
490
491
883k
    if (!Py_UNICODE_ISPRINTABLE(c)) {
492
391
        return MAKE_TOKEN(_PyTokenizer_syntaxerror(tok, "invalid non-printable character U+%04X", c));
493
391
    }
494
495
882k
    if (c == '=' && current != NULL) {
496
8.47k
        _PyLexer_mark_ftstring_debug(tok, current);
497
8.47k
    }
498
499
    /* Punctuation character */
500
882k
    p_start = tok->start;
501
882k
    p_end = tok->cur;
502
882k
    return MAKE_TOKEN(_PyToken_OneChar(c));
503
883k
}
504
505
506
void
507
_PyTokenizer_Get(struct tok_state *tok, struct token *token)
508
2.01M
{
509
2.01M
    _PyToken_Free(token);
510
2.01M
    ftstring_state *current = _PyLexer_CurrentFTString(tok);
511
2.01M
    int result;
512
2.01M
    if (current == NULL) {
513
1.81M
        result = _PyLexer_get_normal(tok, NULL, token);
514
1.81M
    }
515
199k
    else {
516
199k
        switch (current->mode) {
517
136k
        case FTSTRING_MODE_EXPRESSION:
518
136k
            result = _PyLexer_get_normal(tok, current, token);
519
136k
            break;
520
56.9k
        case FTSTRING_MODE_MIDDLE:
521
62.2k
        case FTSTRING_MODE_FORMAT_SPEC:
522
62.2k
            result = _PyLexer_get_ftstring(tok, current, token);
523
62.2k
            break;
524
0
        default:
525
0
            Py_UNREACHABLE();
526
199k
        }
527
199k
    }
528
2.01M
    if (tok_failed(tok) && tok->done != E_INCOMPLETE) {
529
2.65k
        result = ERRORTOKEN;
530
2.65k
    }
531
2.01M
    token->type = result;
532
2.01M
}