/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 | } |