Coverage Report

Created: 2026-07-14 06:16

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/cpython/Parser/lexer/string.c
Line
Count
Source
1
#include "Python.h"
2
#include "pycore_token.h"
3
#include "errcode.h"
4
5
#include "lexer_internal.h"
6
#include "../tokenizer/helpers.h"
7
8
104k
#define MAKE_TOKEN(token_type) _PyLexer_token_setup(tok, token, token_type, p_start, p_end)
9
10
int
11
26.9k
_PyLexer_set_ftstring_expr(struct tok_state* tok, struct token *token, char c) {
12
26.9k
    assert(token != NULL);
13
26.9k
    assert(c == '}' || c == ':' || c == '!');
14
26.9k
    tokenizer_mode *tok_mode = TOK_GET_MODE(tok);
15
16
26.9k
    if (!(tok_mode->in_debug || tok_mode->string_kind == TSTRING) || token->metadata) {
17
13.5k
        return 0;
18
13.5k
    }
19
13.4k
    PyObject *res = NULL;
20
21
    // Look for a # character outside of string literals
22
13.4k
    int hash_detected = 0;
23
13.4k
    int in_string = 0;
24
13.4k
    char quote_char = 0;
25
26
2.10M
    for (Py_ssize_t i = 0; i < tok_mode->last_expr_size - tok_mode->last_expr_end; i++) {
27
2.09M
        char ch = tok_mode->last_expr_buffer[i];
28
29
        // Skip escaped characters
30
2.09M
        if (ch == '\\') {
31
29.8k
            i++;
32
29.8k
            continue;
33
29.8k
        }
34
35
        // Handle quotes
36
2.06M
        if (ch == '"' || ch == '\'') {
37
            // The following if/else block works becase there is an off number
38
            // of quotes in STRING tokens and the lexer only ever reaches this
39
            // function with valid STRING tokens.
40
            // For example: """hello"""
41
            // First quote: in_string = 1
42
            // Second quote: in_string = 0
43
            // Third quote: in_string = 1
44
278k
            if (!in_string) {
45
107k
                in_string = 1;
46
107k
                quote_char = ch;
47
107k
            }
48
171k
            else if (ch == quote_char) {
49
105k
                in_string = 0;
50
105k
            }
51
278k
            continue;
52
278k
        }
53
54
        // Check for # outside strings
55
1.78M
        if (ch == '#' && !in_string) {
56
1.10k
            hash_detected = 1;
57
1.10k
            break;
58
1.10k
        }
59
1.78M
    }
60
    // If we found a # character in the expression, we need to handle comments
61
13.4k
    if (hash_detected) {
62
        // Allocate buffer for processed result
63
1.10k
        char *result = (char *)PyMem_Malloc((tok_mode->last_expr_size - tok_mode->last_expr_end + 1) * sizeof(char));
64
1.10k
        if (!result) {
65
0
            return -1;
66
0
        }
67
68
1.10k
        Py_ssize_t i = 0;  // Input position
69
1.10k
        Py_ssize_t j = 0;  // Output position
70
1.10k
        in_string = 0;     // Whether we're in a string
71
1.10k
        quote_char = 0;    // Current string quote char
72
73
        // Process each character
74
115k
        while (i < tok_mode->last_expr_size - tok_mode->last_expr_end) {
75
114k
            char ch = tok_mode->last_expr_buffer[i];
76
77
            // Handle string quotes
78
114k
            if (ch == '"' || ch == '\'') {
79
                // See comment above to understand this part
80
18.3k
                if (!in_string) {
81
7.48k
                    in_string = 1;
82
7.48k
                    quote_char = ch;
83
10.8k
                } else if (ch == quote_char) {
84
7.48k
                    in_string = 0;
85
7.48k
                }
86
18.3k
                result[j++] = ch;
87
18.3k
            }
88
            // Skip comments
89
96.0k
            else if (ch == '#' && !in_string) {
90
96.9k
                while (i < tok_mode->last_expr_size - tok_mode->last_expr_end &&
91
95.9k
                       tok_mode->last_expr_buffer[i] != '\n') {
92
95.7k
                    i++;
93
95.7k
                }
94
1.21k
                if (i < tok_mode->last_expr_size - tok_mode->last_expr_end) {
95
162
                    result[j++] = '\n';
96
162
                }
97
1.21k
            }
98
            // Copy other chars
99
94.7k
            else {
100
94.7k
                result[j++] = ch;
101
94.7k
            }
102
114k
            i++;
103
114k
        }
104
105
1.10k
        result[j] = '\0';  // Null-terminate the result string
106
1.10k
        res = PyUnicode_DecodeUTF8(result, j, NULL);
107
1.10k
        PyMem_Free(result);
108
12.3k
    } else {
109
12.3k
        res = PyUnicode_DecodeUTF8(
110
12.3k
            tok_mode->last_expr_buffer,
111
12.3k
            tok_mode->last_expr_size - tok_mode->last_expr_end,
112
12.3k
            NULL
113
12.3k
        );
114
12.3k
    }
115
116
13.4k
    if (!res) {
117
0
        return -1;
118
0
    }
119
13.4k
    token->metadata = res;
120
13.4k
    return 0;
121
13.4k
}
122
123
int
124
_PyLexer_update_ftstring_expr(struct tok_state *tok, char cur)
125
72.5k
{
126
72.5k
    assert(tok->cur != NULL);
127
128
72.5k
    Py_ssize_t size = strlen(tok->cur);
129
72.5k
    tokenizer_mode *tok_mode = TOK_GET_MODE(tok);
130
131
72.5k
    switch (cur) {
132
0
       case 0:
133
0
            if (!tok_mode->last_expr_buffer || tok_mode->last_expr_end >= 0) {
134
0
                return 1;
135
0
            }
136
0
            char *new_buffer = PyMem_Realloc(
137
0
                tok_mode->last_expr_buffer,
138
0
                tok_mode->last_expr_size + size
139
0
            );
140
0
            if (new_buffer == NULL) {
141
0
                PyMem_Free(tok_mode->last_expr_buffer);
142
0
                goto error;
143
0
            }
144
0
            tok_mode->last_expr_buffer = new_buffer;
145
0
            strncpy(tok_mode->last_expr_buffer + tok_mode->last_expr_size, tok->cur, size);
146
0
            tok_mode->last_expr_size += size;
147
0
            break;
148
45.5k
        case '{':
149
45.5k
            if (tok_mode->last_expr_buffer != NULL) {
150
28.6k
                PyMem_Free(tok_mode->last_expr_buffer);
151
28.6k
            }
152
45.5k
            tok_mode->last_expr_buffer = PyMem_Malloc(size);
153
45.5k
            if (tok_mode->last_expr_buffer == NULL) {
154
0
                goto error;
155
0
            }
156
45.5k
            tok_mode->last_expr_size = size;
157
45.5k
            tok_mode->last_expr_end = -1;
158
45.5k
            strncpy(tok_mode->last_expr_buffer, tok->cur, size);
159
45.5k
            break;
160
21.0k
        case '}':
161
23.1k
        case '!':
162
23.1k
            tok_mode->last_expr_end = strlen(tok->start);
163
23.1k
            break;
164
3.79k
        case ':':
165
3.79k
            if (tok_mode->last_expr_end == -1) {
166
3.55k
               tok_mode->last_expr_end = strlen(tok->start);
167
3.55k
            }
168
3.79k
            break;
169
0
        default:
170
0
            Py_UNREACHABLE();
171
72.5k
    }
172
72.5k
    return 1;
173
0
error:
174
0
    tok->done = E_NOMEM;
175
0
    return 0;
176
72.5k
}
177
178
int
179
_PyLexer_check_string_prefixes(struct tok_state *tok,
180
                                             int saw_b, int saw_r, int saw_u,
181
26.6k
                                             int saw_f, int saw_t) {
182
    // Supported: rb, rf, rt (in any order)
183
    // Unsupported: ub, ur, uf, ut, bf, bt, ft (in any order)
184
185
26.6k
#define RETURN_SYNTAX_ERROR(PREFIX1, PREFIX2)                             \
186
26.6k
    do {                                                                  \
187
9
        (void)_PyTokenizer_syntaxerror_known_range(                       \
188
9
            tok, (int)(tok->start + 1 - tok->line_start),                 \
189
9
            (int)(tok->cur - tok->line_start),                            \
190
9
            "'" PREFIX1 "' and '" PREFIX2 "' prefixes are incompatible"); \
191
9
        return -1;                                                        \
192
9
    } while (0)
193
194
26.6k
    if (saw_u && saw_b) {
195
1
        RETURN_SYNTAX_ERROR("u", "b");
196
1
    }
197
26.6k
    if (saw_u && saw_r) {
198
1
        RETURN_SYNTAX_ERROR("u", "r");
199
1
    }
200
26.6k
    if (saw_u && saw_f) {
201
1
        RETURN_SYNTAX_ERROR("u", "f");
202
1
    }
203
26.6k
    if (saw_u && saw_t) {
204
1
        RETURN_SYNTAX_ERROR("u", "t");
205
1
    }
206
207
26.6k
    if (saw_b && saw_f) {
208
1
        RETURN_SYNTAX_ERROR("b", "f");
209
1
    }
210
26.6k
    if (saw_b && saw_t) {
211
1
        RETURN_SYNTAX_ERROR("b", "t");
212
1
    }
213
214
26.6k
    if (saw_f && saw_t) {
215
3
        RETURN_SYNTAX_ERROR("f", "t");
216
3
    }
217
218
26.6k
#undef RETURN_SYNTAX_ERROR
219
220
26.6k
    return 0;
221
26.6k
}
222
223
int
224
_PyLexer_scan_fstring_start(struct tok_state *tok, struct token *token, int c)
225
22.4k
{
226
22.4k
    const char *p_start = NULL;
227
22.4k
    const char *p_end = NULL;
228
229
22.4k
    int quote = c;
230
22.4k
    int quote_size = 1;             /* 1 or 3 */
231
232
    /* Nodes of type STRING, especially multi line strings
233
       must be handled differently in order to get both
234
       the starting line number and the column offset right.
235
       (cf. issue 16806) */
236
22.4k
    tok->first_lineno = tok->lineno;
237
22.4k
    tok->multi_line_start = tok->line_start;
238
239
    /* Find the quote size and start of string */
240
22.4k
    int after_quote = tok_nextc(tok);
241
22.4k
    if (after_quote == quote) {
242
2.71k
        int after_after_quote = tok_nextc(tok);
243
2.71k
        if (after_after_quote == quote) {
244
815
            quote_size = 3;
245
815
        }
246
1.89k
        else {
247
            // TODO: Check this
248
1.89k
            tok_backup(tok, after_after_quote);
249
1.89k
            tok_backup(tok, after_quote);
250
1.89k
        }
251
2.71k
    }
252
22.4k
    if (after_quote != quote) {
253
19.7k
        tok_backup(tok, after_quote);
254
19.7k
    }
255
256
257
22.4k
    p_start = tok->start;
258
22.4k
    p_end = tok->cur;
259
22.4k
    if (tok->tok_mode_stack_index + 1 >= MAXFSTRINGLEVEL) {
260
2
        return MAKE_TOKEN(_PyTokenizer_syntaxerror(tok, "too many nested f-strings or t-strings"));
261
2
    }
262
22.4k
    tokenizer_mode *the_current_tok = TOK_NEXT_MODE(tok);
263
22.4k
    the_current_tok->kind = TOK_FSTRING_MODE;
264
22.4k
    the_current_tok->quote = quote;
265
22.4k
    the_current_tok->quote_size = quote_size;
266
22.4k
    the_current_tok->start = tok->start;
267
22.4k
    the_current_tok->multi_line_start = tok->line_start;
268
22.4k
    the_current_tok->first_line = tok->lineno;
269
22.4k
    the_current_tok->start_offset = -1;
270
22.4k
    the_current_tok->multi_line_start_offset = -1;
271
22.4k
    the_current_tok->last_expr_buffer = NULL;
272
22.4k
    the_current_tok->last_expr_size = 0;
273
22.4k
    the_current_tok->last_expr_end = -1;
274
22.4k
    the_current_tok->in_format_spec = 0;
275
22.4k
    the_current_tok->in_debug = 0;
276
277
22.4k
    enum string_kind_t string_kind = FSTRING;
278
22.4k
    switch (*tok->start) {
279
1.38k
        case 'T':
280
6.16k
        case 't':
281
6.16k
            the_current_tok->raw = Py_TOLOWER(*(tok->start + 1)) == 'r';
282
6.16k
            string_kind = TSTRING;
283
6.16k
            break;
284
2.76k
        case 'F':
285
14.4k
        case 'f':
286
14.4k
            the_current_tok->raw = Py_TOLOWER(*(tok->start + 1)) == 'r';
287
14.4k
            break;
288
210
        case 'R':
289
1.85k
        case 'r':
290
1.85k
            the_current_tok->raw = 1;
291
1.85k
            if (Py_TOLOWER(*(tok->start + 1)) == 't') {
292
264
                string_kind = TSTRING;
293
264
            }
294
1.85k
            break;
295
0
        default:
296
0
            Py_UNREACHABLE();
297
22.4k
    }
298
299
22.4k
    the_current_tok->string_kind = string_kind;
300
22.4k
    the_current_tok->curly_bracket_depth = 0;
301
22.4k
    the_current_tok->curly_bracket_expr_start_depth = -1;
302
22.4k
    return string_kind == TSTRING ? MAKE_TOKEN(TSTRING_START) : MAKE_TOKEN(FSTRING_START);
303
22.4k
}
304
305
int
306
_PyLexer_scan_string(struct tok_state *tok, struct token *token, int c)
307
38.6k
{
308
38.6k
    const char *p_start = NULL;
309
38.6k
    const char *p_end = NULL;
310
311
38.6k
    int quote = c;
312
38.6k
    int quote_size = 1;             /* 1 or 3 */
313
38.6k
    int end_quote_size = 0;
314
38.6k
    int has_escaped_quote = 0;
315
316
    /* Nodes of type STRING, especially multi line strings
317
       must be handled differently in order to get both
318
       the starting line number and the column offset right.
319
       (cf. issue 16806) */
320
38.6k
    tok->first_lineno = tok->lineno;
321
38.6k
    tok->multi_line_start = tok->line_start;
322
323
    /* Find the quote size and start of string */
324
38.6k
    c = tok_nextc(tok);
325
38.6k
    if (c == quote) {
326
6.89k
        c = tok_nextc(tok);
327
6.89k
        if (c == quote) {
328
1.01k
            quote_size = 3;
329
1.01k
        }
330
5.88k
        else {
331
5.88k
            end_quote_size = 1;     /* empty string found */
332
5.88k
        }
333
6.89k
    }
334
38.6k
    if (c != quote) {
335
37.6k
        tok_backup(tok, c);
336
37.6k
    }
337
338
    /* Get rest of string */
339
497k
    while (end_quote_size != quote_size) {
340
459k
        c = tok_nextc(tok);
341
459k
        if (tok->done == E_ERROR) {
342
0
            return MAKE_TOKEN(ERRORTOKEN);
343
0
        }
344
459k
        if (tok->done == E_DECODE) {
345
0
            break;
346
0
        }
347
459k
        if (c == EOF || (quote_size == 1 && c == '\n')) {
348
403
            assert(tok->multi_line_start != NULL);
349
            // shift the tok_state's location into
350
            // the start of string, and report the error
351
            // from the initial quote character
352
403
            tok->cur = (char *)tok->start;
353
403
            tok->cur++;
354
403
            tok->line_start = tok->multi_line_start;
355
403
            int start = tok->lineno;
356
403
            tok->lineno = tok->first_lineno;
357
358
403
            if (INSIDE_FSTRING(tok)) {
359
                /* When we are in an f-string, before raising the
360
                 * unterminated string literal error, check whether
361
                 * does the initial quote matches with f-strings quotes
362
                 * and if it is, then this must be a missing '}' token
363
                 * so raise the proper error */
364
47
                tokenizer_mode *the_current_tok = TOK_GET_MODE(tok);
365
47
                if (the_current_tok->quote == quote &&
366
31
                    the_current_tok->quote_size == quote_size) {
367
28
                    return MAKE_TOKEN(_PyTokenizer_syntaxerror(tok,
368
28
                        "%c-string: expecting '}'", TOK_GET_STRING_PREFIX(tok)));
369
28
                }
370
47
            }
371
372
375
            if (quote_size == 3) {
373
29
                _PyTokenizer_syntaxerror(tok, "unterminated triple-quoted string literal"
374
29
                                 " (detected at line %d)", start);
375
29
                if (c != '\n') {
376
29
                    tok->done = E_EOFS;
377
29
                }
378
29
                return MAKE_TOKEN(ERRORTOKEN);
379
29
            }
380
346
            else {
381
346
                if (has_escaped_quote) {
382
5
                    _PyTokenizer_syntaxerror(
383
5
                        tok,
384
5
                        "unterminated string literal (detected at line %d); "
385
5
                        "perhaps you escaped the end quote?",
386
5
                        start
387
5
                    );
388
341
                } else {
389
341
                    _PyTokenizer_syntaxerror(
390
341
                        tok, "unterminated string literal (detected at line %d)", start
391
341
                    );
392
341
                }
393
346
                if (c != '\n') {
394
4
                    tok->done = E_EOLS;
395
4
                }
396
346
                return MAKE_TOKEN(ERRORTOKEN);
397
346
            }
398
375
        }
399
459k
        if (c == quote) {
400
34.9k
            end_quote_size += 1;
401
34.9k
        }
402
424k
        else {
403
424k
            end_quote_size = 0;
404
424k
            if (c == '\\') {
405
26.4k
                c = tok_nextc(tok);  /* skip escaped char */
406
26.4k
                if (c == quote) {  /* but record whether the escaped char was a quote */
407
741
                    has_escaped_quote = 1;
408
741
                }
409
26.4k
                if (c == '\r') {
410
0
                    c = tok_nextc(tok);
411
0
                }
412
26.4k
            }
413
424k
        }
414
459k
    }
415
416
38.2k
    p_start = tok->start;
417
38.2k
    p_end = tok->cur;
418
38.2k
    return MAKE_TOKEN(STRING);
419
38.6k
}
420
421
int
422
_PyLexer_get_fstring_mode(struct tok_state *tok, tokenizer_mode* current_tok, struct token *token)
423
60.9k
{
424
60.9k
    const char *p_start = NULL;
425
60.9k
    const char *p_end = NULL;
426
60.9k
    int end_quote_size = 0;
427
60.9k
    int unicode_escape = 0;
428
429
60.9k
    tok->start = tok->cur;
430
60.9k
    tok->first_lineno = tok->lineno;
431
60.9k
    tok->starting_col_offset = tok->col_offset;
432
433
    // If we start with a bracket, we defer to the normal mode as there is nothing for us to tokenize
434
    // before it.
435
60.9k
    int start_char = tok_nextc(tok);
436
60.9k
    if (start_char == '{') {
437
19.8k
        int peek1 = tok_nextc(tok);
438
19.8k
        tok_backup(tok, peek1);
439
19.8k
        tok_backup(tok, start_char);
440
19.8k
        if (peek1 != '{') {
441
17.7k
            current_tok->curly_bracket_expr_start_depth++;
442
17.7k
            if (current_tok->curly_bracket_expr_start_depth >= MAX_EXPR_NESTING) {
443
3
                return MAKE_TOKEN(_PyTokenizer_syntaxerror(tok,
444
3
                    "%c-string: expressions nested too deeply", TOK_GET_STRING_PREFIX(tok)));
445
3
            }
446
17.7k
            TOK_GET_MODE(tok)->kind = TOK_REGULAR_MODE;
447
17.7k
            return _PyLexer_get_normal_mode(tok, current_tok, token);
448
17.7k
        }
449
19.8k
    }
450
41.1k
    else {
451
41.1k
        tok_backup(tok, start_char);
452
41.1k
    }
453
454
    // Check if we are at the end of the string
455
62.1k
    for (int i = 0; i < current_tok->quote_size; i++) {
456
47.1k
        int quote = tok_nextc(tok);
457
47.1k
        if (quote != current_tok->quote) {
458
28.2k
            tok_backup(tok, quote);
459
28.2k
            goto f_string_middle;
460
28.2k
        }
461
47.1k
    }
462
463
14.9k
    if (current_tok->last_expr_buffer != NULL) {
464
9.69k
        PyMem_Free(current_tok->last_expr_buffer);
465
9.69k
        current_tok->last_expr_buffer = NULL;
466
9.69k
        current_tok->last_expr_size = 0;
467
9.69k
        current_tok->last_expr_end = -1;
468
9.69k
    }
469
470
14.9k
    p_start = tok->start;
471
14.9k
    p_end = tok->cur;
472
14.9k
    tok->tok_mode_stack_index--;
473
14.9k
    return MAKE_TOKEN(FTSTRING_END(current_tok));
474
475
28.2k
f_string_middle:
476
477
    // TODO: This is a bit of a hack, but it works for now. We need to find a better way to handle
478
    // this.
479
28.2k
    tok->multi_line_start = tok->line_start;
480
198k
    while (end_quote_size != current_tok->quote_size) {
481
192k
        int c = tok_nextc(tok);
482
192k
        if (tok->done == E_ERROR || tok->done == E_DECODE) {
483
0
            return MAKE_TOKEN(ERRORTOKEN);
484
0
        }
485
192k
        int in_format_spec = (
486
192k
                current_tok->in_format_spec
487
13.1k
                &&
488
13.1k
                INSIDE_FSTRING_EXPR(current_tok)
489
192k
        );
490
491
192k
       if (c == EOF || (current_tok->quote_size == 1 && c == '\n')) {
492
387
            if (tok->decoding_erred) {
493
0
                return MAKE_TOKEN(ERRORTOKEN);
494
0
            }
495
496
            // If we are in a format spec and we found a newline,
497
            // it means that the format spec ends here and we should
498
            // return to the regular mode.
499
387
            if (in_format_spec && c == '\n') {
500
45
                if (current_tok->quote_size == 1) {
501
45
                    return MAKE_TOKEN(
502
45
                        _PyTokenizer_syntaxerror(
503
45
                            tok,
504
45
                            "%c-string: newlines are not allowed in format specifiers for single quoted %c-strings",
505
45
                            TOK_GET_STRING_PREFIX(tok), TOK_GET_STRING_PREFIX(tok)
506
45
                        )
507
45
                    );
508
45
                }
509
0
                tok_backup(tok, c);
510
0
                TOK_GET_MODE(tok)->kind = TOK_REGULAR_MODE;
511
0
                current_tok->in_format_spec = 0;
512
0
                p_start = tok->start;
513
0
                p_end = tok->cur;
514
0
                return MAKE_TOKEN(FTSTRING_MIDDLE(current_tok));
515
45
            }
516
517
387
            assert(tok->multi_line_start != NULL);
518
            // shift the tok_state's location into
519
            // the start of string, and report the error
520
            // from the initial quote character
521
342
            tok->cur = (char *)current_tok->start;
522
342
            tok->cur++;
523
342
            tok->line_start = current_tok->multi_line_start;
524
342
            int start = tok->lineno;
525
526
342
            tokenizer_mode *the_current_tok = TOK_GET_MODE(tok);
527
342
            tok->lineno = the_current_tok->first_line;
528
529
342
            if (current_tok->quote_size == 3) {
530
28
                _PyTokenizer_syntaxerror(tok,
531
28
                                    "unterminated triple-quoted %c-string literal"
532
28
                                    " (detected at line %d)",
533
28
                                    TOK_GET_STRING_PREFIX(tok), start);
534
28
                if (c != '\n') {
535
28
                    tok->done = E_EOFS;
536
28
                }
537
28
                return MAKE_TOKEN(ERRORTOKEN);
538
28
            }
539
314
            else {
540
314
                return MAKE_TOKEN(_PyTokenizer_syntaxerror(tok,
541
314
                                    "unterminated %c-string literal (detected at"
542
314
                                    " line %d)", TOK_GET_STRING_PREFIX(tok), start));
543
314
            }
544
342
        }
545
546
191k
        if (c == current_tok->quote) {
547
9.82k
            end_quote_size += 1;
548
9.82k
            continue;
549
181k
        } else {
550
181k
            end_quote_size = 0;
551
181k
        }
552
553
181k
        if (c == '{') {
554
16.6k
            if (!_PyLexer_update_ftstring_expr(tok, c)) {
555
0
                return MAKE_TOKEN(ENDMARKER);
556
0
            }
557
16.6k
            int peek = tok_nextc(tok);
558
16.6k
            if (peek != '{' || in_format_spec) {
559
13.6k
                tok_backup(tok, peek);
560
13.6k
                tok_backup(tok, c);
561
13.6k
                current_tok->curly_bracket_expr_start_depth++;
562
13.6k
                if (current_tok->curly_bracket_expr_start_depth >= MAX_EXPR_NESTING) {
563
4
                    return MAKE_TOKEN(_PyTokenizer_syntaxerror(tok,
564
4
                        "%c-string: expressions nested too deeply", TOK_GET_STRING_PREFIX(tok)));
565
4
                }
566
13.6k
                TOK_GET_MODE(tok)->kind = TOK_REGULAR_MODE;
567
13.6k
                current_tok->in_format_spec = 0;
568
13.6k
                p_start = tok->start;
569
13.6k
                p_end = tok->cur;
570
13.6k
            } else {
571
2.93k
                p_start = tok->start;
572
2.93k
                p_end = tok->cur - 1;
573
2.93k
            }
574
16.6k
            return MAKE_TOKEN(FTSTRING_MIDDLE(current_tok));
575
165k
        } else if (c == '}') {
576
4.91k
            if (unicode_escape) {
577
304
                p_start = tok->start;
578
304
                p_end = tok->cur;
579
304
                return MAKE_TOKEN(FTSTRING_MIDDLE(current_tok));
580
304
            }
581
4.60k
            int peek = tok_nextc(tok);
582
583
            // The tokenizer can only be in the format spec if we have already completed the expression
584
            // scanning (indicated by the end of the expression being set) and we are not at the top level
585
            // of the bracket stack (-1 is the top level). Since format specifiers can't legally use double
586
            // brackets, we can bypass it here.
587
4.60k
            int cursor = current_tok->curly_bracket_depth;
588
4.60k
            if (peek == '}' && !in_format_spec && cursor == 0) {
589
854
                p_start = tok->start;
590
854
                p_end = tok->cur - 1;
591
3.75k
            } else {
592
3.75k
                tok_backup(tok, peek);
593
3.75k
                tok_backup(tok, c);
594
3.75k
                TOK_GET_MODE(tok)->kind = TOK_REGULAR_MODE;
595
3.75k
                current_tok->in_format_spec = 0;
596
3.75k
                p_start = tok->start;
597
3.75k
                p_end = tok->cur;
598
3.75k
            }
599
4.60k
            return MAKE_TOKEN(FTSTRING_MIDDLE(current_tok));
600
160k
        } else if (c == '\\') {
601
5.60k
            int peek = tok_nextc(tok);
602
5.60k
            if (peek == '\r') {
603
0
                peek = tok_nextc(tok);
604
0
            }
605
            // Special case when the backslash is right before a curly
606
            // brace. We have to restore and return the control back
607
            // to the loop for the next iteration.
608
5.60k
            if (peek == '{' || peek == '}') {
609
739
                if (!current_tok->raw) {
610
673
                    if (_PyTokenizer_warn_invalid_escape_sequence(tok, peek)) {
611
0
                        return MAKE_TOKEN(ERRORTOKEN);
612
0
                    }
613
673
                }
614
739
                tok_backup(tok, peek);
615
739
                continue;
616
739
            }
617
618
4.86k
            if (!current_tok->raw) {
619
4.64k
                if (peek == 'N') {
620
                    /* Handle named unicode escapes (\N{BULLET}) */
621
520
                    peek = tok_nextc(tok);
622
520
                    if (peek == '{') {
623
318
                        unicode_escape = 1;
624
318
                    } else {
625
202
                        tok_backup(tok, peek);
626
202
                    }
627
520
                }
628
4.64k
            } /* else {
629
                skip the escaped character
630
            }*/
631
4.86k
        }
632
181k
    }
633
634
    // Backup the f-string quotes to emit a final FSTRING_MIDDLE and
635
    // add the quotes to the FSTRING_END in the next tokenizer iteration.
636
13.7k
    for (int i = 0; i < current_tok->quote_size; i++) {
637
7.42k
        tok_backup(tok, current_tok->quote);
638
7.42k
    }
639
6.27k
    p_start = tok->start;
640
6.27k
    p_end = tok->cur;
641
6.27k
    return MAKE_TOKEN(FTSTRING_MIDDLE(current_tok));
642
28.2k
}