Coverage Report

Created: 2026-10-31 06:21

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
105k
#define MAKE_TOKEN(token_type) _PyLexer_token_setup(tok, token, token_type, p_start, p_end)
9
10
static int
11
string_error_token(struct tok_state *tok, struct token *token,
12
                   _PyTok_Off start, _PyTok_Loc location)
13
686
{
14
686
    tok->diagnostic = (_PyTokenizer_Diagnostic){
15
686
        .location = {location.lineno, location.byte_col + 1},
16
686
        .text_span = {start - location.byte_col, tok->inp},
17
686
    };
18
686
    int type = _PyLexer_token_setup(tok, token, ERRORTOKEN, -1, -1);
19
686
    token->start_loc = location;
20
686
    token->end_loc = (_PyTok_Loc){location.lineno, -1};
21
686
    return type;
22
686
}
23
24
static int
25
incomplete_string_token(struct tok_state *tok, struct token *token,
26
                        int type, _PyTok_Off start, _PyTok_Loc location)
27
0
{
28
0
    string_error_token(tok, token, start, location);
29
0
    tok->done = E_INCOMPLETE;
30
0
    token->is_incomplete = 1;
31
0
    return type;
32
0
}
33
34
int
35
_PyLexer_record_ftstring_comment(struct tok_state *tok, ftstring_state *state,
36
                                 _PyTok_Off start, _PyTok_Off end)
37
1.53k
{
38
1.53k
    assert(state == _PyLexer_CurrentFTString(tok) && state->mode == FTSTRING_MODE_EXPRESSION);
39
1.53k
    if (state->expr_span.end >= 0) {
40
215
        return 0;
41
215
    }
42
1.53k
    assert(state->expr_span.start >= 0);
43
1.31k
    tokenizer_comments *comments = state->comments;
44
1.31k
    if (comments == NULL || comments->count == comments->capacity) {
45
375
        Py_ssize_t max_capacity = (PY_SSIZE_T_MAX -
46
375
            (Py_ssize_t)sizeof(*comments)) /
47
375
            (Py_ssize_t)sizeof(*comments->spans);
48
375
        if (comments != NULL && comments->capacity > max_capacity / 2) {
49
0
            PyErr_NoMemory();
50
0
            return -1;
51
0
        }
52
375
        Py_ssize_t capacity = comments == NULL ? 4 : comments->capacity * 2;
53
375
        size_t size = sizeof(*comments) +
54
375
            (size_t)capacity * sizeof(*comments->spans);
55
375
        tokenizer_comments *resized = PyMem_Realloc(comments, size);
56
375
        if (resized == NULL) {
57
0
            PyErr_NoMemory();
58
0
            return -1;
59
0
        }
60
375
        comments = resized;
61
375
        if (state->comments == NULL) {
62
278
            comments->count = 0;
63
278
        }
64
375
        comments->capacity = capacity;
65
375
        state->comments = comments;
66
375
    }
67
1.31k
    comments->spans[comments->count++] =
68
1.31k
        (_PyTok_Span){start, end};
69
1.31k
    return 0;
70
1.31k
}
71
72
static int
73
finish_ftstring_expr(struct tok_state *tok, ftstring_state *state,
74
                     struct token *token)
75
32.5k
{
76
32.5k
    assert(token != NULL && state == _PyLexer_CurrentFTString(tok));
77
32.5k
    assert(state->mode == FTSTRING_MODE_EXPRESSION && tok->start >= 0);
78
79
32.5k
    if (state->expr_span.end >= 0) {
80
5.58k
        return 0;
81
5.58k
    }
82
32.5k
    assert(state->expr_span.start >= 0);
83
26.9k
    state->expr_span.end = tok->start;
84
26.9k
    int tstring_interpolation = _PyLexer_IsTString(state->kind) &&
85
7.89k
        state->replacement_depth == 1;
86
26.9k
    if (!(state->debug_expr || tstring_interpolation) || token->metadata) {
87
13.0k
        return 0;
88
13.0k
    }
89
13.9k
    tokenizer_comments *comments = state->comments;
90
13.9k
    PyObject *res;
91
13.9k
    if (comments != NULL && comments->count > 0) {
92
121
        Py_ssize_t stripped_size =
93
121
            state->expr_span.end - state->expr_span.start;
94
121
        Py_ssize_t comment_count = 0;
95
469
        for (Py_ssize_t i = 0; i < comments->count; i++) {
96
348
            _PyTok_Span comment = comments->spans[i];
97
348
            assert(_PyTok_SpanIsValid(comment));
98
348
            assert(comment.start >= (i == 0 ? state->expr_span.start
99
348
                                           : comments->spans[i - 1].end));
100
348
            if (comment.start >= state->expr_span.end) {
101
0
                break;
102
0
            }
103
348
            assert(comment.end <= state->expr_span.end);
104
348
            stripped_size -= comment.end - comment.start;
105
348
            comment_count++;
106
348
        }
107
121
        char *stripped = PyMem_Malloc((size_t)stripped_size);
108
121
        if (stripped == NULL) {
109
0
            PyErr_NoMemory();
110
0
            return -1;
111
0
        }
112
121
        _PyTok_Span kept = {state->expr_span.start, state->expr_span.start};
113
121
        Py_ssize_t stripped_len = 0;
114
469
        for (Py_ssize_t i = 0; i < comment_count; i++) {
115
348
            kept.end = comments->spans[i].start;
116
348
            Py_ssize_t length;
117
348
            const char *text = _PyTok_SourceSpanView(
118
348
                &tok->source, kept, &length);
119
348
            memcpy(stripped + stripped_len, text, (size_t)length);
120
348
            stripped_len += length;
121
348
            kept.start = comments->spans[i].end;
122
348
        }
123
121
        kept.end = state->expr_span.end;
124
121
        Py_ssize_t length;
125
121
        const char *text = _PyTok_SourceSpanView(&tok->source, kept, &length);
126
121
        memcpy(stripped + stripped_len, text, (size_t)length);
127
121
        res = PyUnicode_DecodeUTF8(stripped, stripped_size, NULL);
128
121
        PyMem_Free(stripped);
129
121
    }
130
13.7k
    else {
131
13.7k
        Py_ssize_t expr_len;
132
13.7k
        const char *expr = _PyTok_SourceSpanView(
133
13.7k
            &tok->source, state->expr_span, &expr_len);
134
13.7k
        res = PyUnicode_DecodeUTF8(expr, expr_len, NULL);
135
13.7k
    }
136
137
13.9k
    if (!res) {
138
0
        return -1;
139
0
    }
140
13.9k
    token->metadata = res;
141
13.9k
    return 0;
142
13.9k
}
143
144
static int
145
begin_ftstring_expr(struct tok_state *tok, ftstring_state *state,
146
                    _PyTok_Off expr_start)
147
33.7k
{
148
33.7k
    assert(state->mode != FTSTRING_MODE_EXPRESSION);
149
33.7k
    state->expr_span = (_PyTok_Span){expr_start, -1};
150
33.7k
    if (state->comments != NULL) {
151
599
        state->comments->count = 0;
152
599
    }
153
33.7k
    if (state->replacement_depth >= MAX_EXPR_NESTING) {
154
4
        _PyTokenizer_syntaxerror(
155
4
            tok, "%c-string: expressions nested too deeply",
156
4
            _PyLexer_StringPrefix(state->kind));
157
4
        return -1;
158
4
    }
159
33.7k
    state->replacement_depth++;
160
33.7k
    state->mode = FTSTRING_MODE_EXPRESSION;
161
33.7k
    state->debug_expr = 0;
162
33.7k
    return 0;
163
33.7k
}
164
165
int
166
_PyLexer_ftstring_punctuation(struct tok_state *tok, ftstring_state *state,
167
                              struct token *token, int c)
168
33.5k
{
169
33.5k
    assert(state->mode == FTSTRING_MODE_EXPRESSION);
170
33.5k
    assert(c == ':' || c == '}' || c == '!');
171
33.5k
    if (_PyLexer_FTStringBracketDepth(tok, state) != state->replacement_depth) {
172
826
        return 0;
173
826
    }
174
32.7k
    if (c == '!') {
175
2.01k
        int next = tok_nextc(tok);
176
2.01k
        tok_backup(tok, next);
177
2.01k
        if (next == '=') {
178
196
            return 0;
179
196
        }
180
2.01k
    }
181
32.5k
    if (finish_ftstring_expr(tok, state, token) < 0) {
182
0
        return -1;
183
0
    }
184
32.5k
    if (c == ':') {
185
5.26k
        state->mode = FTSTRING_MODE_FORMAT_SPEC;
186
5.26k
        return COLON;
187
5.26k
    }
188
27.2k
    return 0;
189
32.5k
}
190
191
int
192
_PyLexer_close_ftstring_expr(struct tok_state *tok, ftstring_state *state,
193
                             int c)
194
26.8k
{
195
26.8k
    assert(state->mode == FTSTRING_MODE_EXPRESSION);
196
26.8k
    assert(c == ')' || c == ']' || c == '}');
197
26.8k
    int depth = _PyLexer_FTStringBracketDepth(tok, state);
198
26.8k
    if (depth < 0) {
199
0
        _PyTokenizer_syntaxerror(tok, "%c-string: unmatched '%c'",
200
0
                                _PyLexer_StringPrefix(state->kind), c);
201
0
        return -1;
202
0
    }
203
26.8k
    if (c == '}' && depth == state->replacement_depth - 1) {
204
25.4k
        state->replacement_depth--;
205
25.4k
        state->mode = FTSTRING_MODE_MIDDLE;
206
25.4k
        state->debug_expr = 0;
207
25.4k
    }
208
26.8k
    return 0;
209
26.8k
}
210
211
void
212
_PyLexer_mark_ftstring_debug(struct tok_state *tok, ftstring_state *state)
213
8.47k
{
214
8.47k
    assert(state->mode == FTSTRING_MODE_EXPRESSION);
215
8.47k
    if (_PyLexer_FTStringBracketDepth(tok, state) == state->replacement_depth) {
216
8.12k
        state->debug_expr = 1;
217
8.12k
    }
218
8.47k
}
219
220
int
221
_PyLexer_check_string_prefixes(struct tok_state *tok,
222
                                             int saw_b, int saw_r, int saw_u,
223
26.6k
                                             int saw_f, int saw_t) {
224
    // Supported: rb, rf, rt (in any order)
225
    // Unsupported: ub, ur, uf, ut, bf, bt, ft (in any order)
226
227
26.6k
#define RETURN_SYNTAX_ERROR(PREFIX1, PREFIX2)                             \
228
26.6k
    do {                                                                  \
229
8
        (void)_PyTokenizer_syntaxerror_known_range(                       \
230
8
            tok, (int)(tok->start + 1 - tok->line_start),                 \
231
8
            (int)(tok->cur - tok->line_start),                            \
232
8
            "'" PREFIX1 "' and '" PREFIX2 "' prefixes are incompatible"); \
233
8
        return -1;                                                        \
234
8
    } while (0)
235
236
26.6k
    if (saw_u && saw_b) {
237
1
        RETURN_SYNTAX_ERROR("u", "b");
238
1
    }
239
26.6k
    if (saw_u && saw_r) {
240
1
        RETURN_SYNTAX_ERROR("u", "r");
241
1
    }
242
26.6k
    if (saw_u && saw_f) {
243
1
        RETURN_SYNTAX_ERROR("u", "f");
244
1
    }
245
26.6k
    if (saw_u && saw_t) {
246
1
        RETURN_SYNTAX_ERROR("u", "t");
247
1
    }
248
249
26.6k
    if (saw_b && saw_f) {
250
2
        RETURN_SYNTAX_ERROR("b", "f");
251
2
    }
252
26.6k
    if (saw_b && saw_t) {
253
1
        RETURN_SYNTAX_ERROR("b", "t");
254
1
    }
255
256
26.6k
    if (saw_f && saw_t) {
257
1
        RETURN_SYNTAX_ERROR("f", "t");
258
1
    }
259
260
26.6k
#undef RETURN_SYNTAX_ERROR
261
262
26.6k
    return 0;
263
26.6k
}
264
265
int
266
_PyLexer_scan_fstring_start(struct tok_state *tok, struct token *token,
267
                            int c, ftstring_kind kind)
268
21.7k
{
269
21.7k
    _PyTok_Off p_start = -1;
270
21.7k
    _PyTok_Off p_end = -1;
271
272
21.7k
    int quote = c;
273
21.7k
    int quote_size = 1;             /* 1 or 3 */
274
275
    /* Find the quote size and start of string */
276
21.7k
    int after_quote = tok_nextc(tok);
277
21.7k
    if (after_quote == quote) {
278
2.87k
        int after_after_quote = tok_nextc(tok);
279
2.87k
        if (after_after_quote == quote) {
280
791
            quote_size = 3;
281
791
        }
282
2.08k
        else {
283
            // TODO: Check this
284
2.08k
            tok_backup(tok, after_after_quote);
285
2.08k
            tok_backup(tok, after_quote);
286
2.08k
        }
287
2.87k
    }
288
21.7k
    if (after_quote != quote) {
289
18.8k
        tok_backup(tok, after_quote);
290
18.8k
    }
291
292
21.7k
    p_start = tok->start;
293
21.7k
    p_end = tok->cur;
294
21.7k
    ftstring_state *state = _PyLexer_PushFTString(tok);
295
21.7k
    if (state == NULL) {
296
1
        return MAKE_TOKEN(ERRORTOKEN);
297
1
    }
298
21.7k
    state->mode = FTSTRING_MODE_MIDDLE;
299
21.7k
    state->quote = quote;
300
21.7k
    state->quote_size = quote_size;
301
21.7k
    state->paren_level = tok->level;
302
21.7k
    state->start = tok->start;
303
21.7k
    state->start_loc = tok->start_loc;
304
21.7k
    state->expr_span = (_PyTok_Span){-1, -1};
305
306
21.7k
    state->kind = kind;
307
21.7k
    return _PyLexer_IsTString(kind)
308
21.7k
        ? MAKE_TOKEN(TSTRING_START) : MAKE_TOKEN(FSTRING_START);
309
21.7k
}
310
311
int
312
_PyLexer_scan_string(struct tok_state *tok, struct token *token, int c)
313
39.2k
{
314
39.2k
    _PyTok_Off p_start = -1;
315
39.2k
    _PyTok_Off p_end = -1;
316
317
39.2k
    int quote = c;
318
39.2k
    int quote_size = 1;             /* 1 or 3 */
319
39.2k
    int end_quote_size = 0;
320
39.2k
    int has_escaped_quote = 0;
321
322
    /* Find the quote size and start of string */
323
39.2k
    c = tok_nextc(tok);
324
39.2k
    if (c == quote) {
325
7.74k
        c = tok_nextc(tok);
326
7.74k
        if (c == quote) {
327
726
            quote_size = 3;
328
726
        }
329
7.01k
        else {
330
7.01k
            end_quote_size = 1;     /* empty string found */
331
7.01k
        }
332
7.74k
    }
333
39.2k
    if (c != quote) {
334
38.5k
        tok_backup(tok, c);
335
38.5k
    }
336
337
    /* Get rest of string */
338
421k
    while (end_quote_size != quote_size) {
339
382k
        c = tok_nextc(tok);
340
382k
        if (tok->done == E_ERROR) {
341
0
            return MAKE_TOKEN(ERRORTOKEN);
342
0
        }
343
382k
        if (tok->done == E_DECODE) {
344
0
            break;
345
0
        }
346
382k
        if (c == EOF || (quote_size == 1 && c == '\n')) {
347
414
            if (tok_failed(tok)) {
348
0
                return MAKE_TOKEN(ERRORTOKEN);
349
0
            }
350
414
            if (c == EOF && tok->allow_incomplete_input) {
351
0
                return incomplete_string_token(tok, token, STRING,
352
0
                                               tok->start, tok->start_loc);
353
0
            }
354
414
            int end_lineno = tok->lineno;
355
414
            _PyTok_Loc location = tok->start_loc;
356
414
            const char *line = _PyTok_SourcePointer(
357
414
                &tok->source, tok->start - location.byte_col);
358
414
            Py_ssize_t cursor_offset = (Py_ssize_t)location.byte_col + 1;
359
360
414
            const ftstring_state *state = _PyLexer_CurrentFTString(tok);
361
414
            if (state != NULL) {
362
                /* A matching quote may have been intended to close the
363
                 * surrounding formatted string instead of opening a string
364
                 * inside a replacement field. */
365
47
                if (state->quote == quote && state->quote_size == quote_size) {
366
29
                    int level = state->paren_level + state->replacement_depth - 1;
367
29
                    assert(level >= 0 && level < tok->level);
368
29
                    assert(tok->parenstack[level] == '{');
369
29
                    int lineno = tok->parenlinenostack[level];
370
29
                    if (lineno != location.lineno) {
371
5
                        _PyTokenizer_syntaxerror_at(
372
5
                            tok, line, cursor_offset, location.lineno, -1, -1,
373
5
                            "%c-string: expecting '}' to close '{' on line %d",
374
5
                            _PyLexer_StringPrefix(state->kind), lineno);
375
5
                    }
376
24
                    else {
377
24
                        _PyTokenizer_syntaxerror_at(
378
24
                            tok, line, cursor_offset, location.lineno, -1, -1,
379
24
                            "%c-string: expecting '}'",
380
24
                            _PyLexer_StringPrefix(state->kind));
381
24
                    }
382
29
                    return string_error_token(tok, token, tok->start, location);
383
29
                }
384
47
            }
385
386
385
            if (quote_size == 3) {
387
29
                _PyTokenizer_syntaxerror_at(
388
29
                    tok, line, cursor_offset, location.lineno, -1, -1,
389
29
                    "unterminated triple-quoted string literal"
390
29
                    " (detected at line %d)", end_lineno);
391
29
                if (c != '\n') {
392
29
                    tok->done = E_EOFS;
393
29
                }
394
29
                return string_error_token(tok, token, tok->start, location);
395
29
            }
396
356
            else {
397
356
                if (has_escaped_quote) {
398
9
                    _PyTokenizer_syntaxerror_at(
399
9
                        tok, line, cursor_offset, location.lineno, -1, -1,
400
9
                        "unterminated string literal (detected at line %d); "
401
9
                        "perhaps you escaped the end quote?",
402
9
                        end_lineno
403
9
                    );
404
347
                } else {
405
347
                    _PyTokenizer_syntaxerror_at(
406
347
                        tok, line, cursor_offset, location.lineno, -1, -1,
407
347
                        "unterminated string literal (detected at line %d)", end_lineno
408
347
                    );
409
347
                }
410
356
                if (c != '\n') {
411
9
                    tok->done = E_EOLS;
412
9
                }
413
356
                return string_error_token(tok, token, tok->start, location);
414
356
            }
415
385
        }
416
382k
        if (c == quote) {
417
33.8k
            end_quote_size += 1;
418
33.8k
        }
419
348k
        else {
420
348k
            end_quote_size = 0;
421
348k
            if (c == '\\') {
422
30.6k
                c = tok_nextc(tok);  /* skip escaped char */
423
30.6k
                if (c == quote) {  /* but record whether the escaped char was a quote */
424
996
                    has_escaped_quote = 1;
425
996
                }
426
30.6k
                if (c == '\r') {
427
0
                    c = tok_nextc(tok);
428
0
                }
429
30.6k
            }
430
348k
        }
431
382k
    }
432
433
38.8k
    p_start = tok->start;
434
38.8k
    p_end = tok->cur;
435
38.8k
    return MAKE_TOKEN(STRING);
436
39.2k
}
437
438
int
439
_PyLexer_get_ftstring(struct tok_state *tok, ftstring_state *current, struct token *token)
440
62.2k
{
441
62.2k
    assert(current == _PyLexer_CurrentFTString(tok) && current->mode != FTSTRING_MODE_EXPRESSION);
442
62.2k
    assert((current->quote_size == 1 || current->quote_size == 3) &&
443
62.2k
           current->replacement_depth <= MAX_EXPR_NESTING);
444
62.2k
    _PyTok_Off p_start = -1;
445
62.2k
    _PyTok_Off p_end = -1;
446
62.2k
    int token_type;
447
62.2k
    int end_quote_size = 0;
448
62.2k
    int unicode_escape = 0;
449
62.2k
    int quote = current->quote;
450
62.2k
    int quote_size = current->quote_size;
451
62.2k
    int in_format_spec = current->mode == FTSTRING_MODE_FORMAT_SPEC;
452
62.2k
    int raw = _PyLexer_IsRawString(current->kind);
453
454
62.2k
    tok->start = tok->cur;
455
62.2k
    tok->start_loc = (_PyTok_Loc){tok->lineno, _PyLexer_ByteColumn(tok)};
456
457
265k
    while (end_quote_size != quote_size) {
458
245k
        int c = tok_nextc(tok);
459
245k
        if (tok->done == E_ERROR || tok->done == E_DECODE) {
460
0
            return MAKE_TOKEN(ERRORTOKEN);
461
0
        }
462
463
245k
        if (c == EOF || (quote_size == 1 && c == '\n')) {
464
320
            if (tok_failed(tok)) {
465
0
                return MAKE_TOKEN(ERRORTOKEN);
466
0
            }
467
320
            if (c == EOF && tok->allow_incomplete_input) {
468
0
                int type = incomplete_string_token(tok, token,
469
0
                    FTSTRING_MIDDLE(current), current->start, current->start_loc);
470
                // Preserve the actual prefix for incremental escape decoding.
471
0
                _PyTok_Off end = tok->cur;
472
0
                if (end > tok->start && tok->implicit_newline) {
473
0
                    end--;
474
0
                }
475
0
                token->span = (_PyTok_Span){tok->start, end};
476
0
                token->start_loc = tok->start_loc;
477
0
                token->end_loc = (_PyTok_Loc){tok->lineno, (int)(end - tok->line_start)};
478
0
                token->is_raw = raw;
479
0
                return type;
480
0
            }
481
482
320
            if (in_format_spec && c == '\n') {
483
48
                return MAKE_TOKEN(_PyTokenizer_syntaxerror(
484
48
                    tok,
485
48
                    "%c-string: newlines are not allowed in format specifiers for single quoted %c-strings",
486
48
                    _PyLexer_StringPrefix(current->kind),
487
48
                    _PyLexer_StringPrefix(current->kind)));
488
48
            }
489
490
272
            int end_lineno = tok->lineno;
491
272
            _PyTok_Loc location = current->start_loc;
492
272
            const char *line = _PyTok_SourcePointer(
493
272
                &tok->source, current->start - location.byte_col);
494
272
            Py_ssize_t cursor_offset = (Py_ssize_t)location.byte_col + 1;
495
496
272
            if (quote_size == 3) {
497
19
                _PyTokenizer_syntaxerror_at(
498
19
                    tok, line, cursor_offset, location.lineno, -1, -1,
499
19
                    "unterminated triple-quoted %c-string literal"
500
19
                    " (detected at line %d)",
501
19
                    _PyLexer_StringPrefix(current->kind), end_lineno);
502
19
                if (c != '\n') {
503
19
                    tok->done = E_EOFS;
504
19
                }
505
19
                return string_error_token(tok, token,
506
19
                    current->start, location);
507
19
            }
508
253
            else {
509
253
                _PyTokenizer_syntaxerror_at(
510
253
                    tok, line, cursor_offset, location.lineno, -1, -1,
511
253
                    "unterminated %c-string literal (detected at line %d)",
512
253
                    _PyLexer_StringPrefix(current->kind), end_lineno);
513
253
                return string_error_token(tok, token,
514
253
                    current->start, location);
515
253
            }
516
272
        }
517
518
245k
        if (c == quote) {
519
28.4k
            end_quote_size += 1;
520
28.4k
            continue;
521
217k
        } else {
522
217k
            end_quote_size = 0;
523
217k
        }
524
525
217k
        if (c == '{') {
526
36.9k
            int peek = tok_nextc(tok);
527
36.9k
            if (peek != '{' || in_format_spec) {
528
33.7k
                tok_backup(tok, peek);
529
33.7k
                _PyTok_Off expr_start = tok->cur;
530
33.7k
                tok_backup(tok, c);
531
33.7k
                if (begin_ftstring_expr(tok, current, expr_start) < 0) {
532
4
                    return MAKE_TOKEN(ERRORTOKEN);
533
4
                }
534
33.7k
                p_start = tok->start;
535
33.7k
                p_end = tok->cur;
536
33.7k
                if (p_start == p_end) {
537
17.2k
                    return _PyLexer_get_normal(tok, current, token);
538
17.2k
                }
539
33.7k
            } else {
540
3.14k
                p_start = tok->start;
541
3.14k
                p_end = tok->cur - 1;
542
3.14k
            }
543
19.6k
            goto emit_middle;
544
180k
        } else if (c == '}') {
545
5.12k
            if (unicode_escape) {
546
239
                p_start = tok->start;
547
239
                p_end = tok->cur;
548
239
                goto emit_middle;
549
239
            }
550
4.88k
            int peek = tok_nextc(tok);
551
552
4.88k
            int bracket_depth = _PyLexer_FTStringBracketDepth(tok, current);
553
4.88k
            if (peek == '}' && !in_format_spec && bracket_depth == 0) {
554
963
                p_start = tok->start;
555
963
                p_end = tok->cur - 1;
556
963
            }
557
3.92k
            else {
558
3.92k
                tok_backup(tok, peek);
559
3.92k
                if (!in_format_spec && bracket_depth == 0) {
560
64
                    if (tok->start == tok->cur - 1) {
561
43
                        return MAKE_TOKEN(_PyTokenizer_syntaxerror(
562
43
                            tok, "%c-string: single '}' is not allowed",
563
43
                            _PyLexer_StringPrefix(current->kind)));
564
43
                    }
565
21
                    tok_backup(tok, c);
566
21
                    p_start = tok->start;
567
21
                    p_end = tok->cur;
568
21
                    goto emit_middle;
569
64
                }
570
3.85k
                tok_backup(tok, c);
571
3.85k
                current->mode = FTSTRING_MODE_EXPRESSION;
572
3.85k
                p_start = tok->start;
573
3.85k
                p_end = tok->cur;
574
3.85k
            }
575
4.82k
            goto emit_middle;
576
175k
        } else if (c == '\\') {
577
5.76k
            int peek = tok_nextc(tok);
578
5.76k
            if (peek == '\r') {
579
0
                peek = tok_nextc(tok);
580
0
            }
581
5.76k
            if (peek == '{' || peek == '}') {
582
1.37k
                if (!raw) {
583
1.12k
                    if (_PyTokenizer_warn_invalid_escape_sequence(tok, peek)) {
584
0
                        return MAKE_TOKEN(ERRORTOKEN);
585
0
                    }
586
1.12k
                }
587
1.37k
                tok_backup(tok, peek);
588
1.37k
                continue;
589
1.37k
            }
590
591
4.38k
            if (!raw) {
592
4.14k
                if (peek == 'N') {
593
                    /* Handle named unicode escapes (\N{BULLET}) */
594
490
                    peek = tok_nextc(tok);
595
490
                    if (peek == '{') {
596
281
                        unicode_escape = 1;
597
281
                    } else {
598
209
                        tok_backup(tok, peek);
599
209
                    }
600
490
                }
601
4.14k
            }
602
4.38k
        }
603
217k
    }
604
605
19.9k
    p_start = tok->start;
606
19.9k
    p_end = tok->cur;
607
19.9k
    if (p_end - quote_size == p_start) {
608
14.4k
        int end_token = FTSTRING_END(current);
609
14.4k
        _PyLexer_PopFTString(tok);
610
14.4k
        return MAKE_TOKEN(end_token);
611
14.4k
    }
612
12.0k
    for (int i = 0; i < quote_size; i++) {
613
6.56k
        tok_backup(tok, quote);
614
6.56k
    }
615
5.47k
    p_end = tok->cur;
616
30.1k
emit_middle:
617
30.1k
    token_type = MAKE_TOKEN(FTSTRING_MIDDLE(current));
618
30.1k
    token->is_raw = raw;
619
30.1k
    return token_type;
620
5.47k
}