Skip to content

Commit b0926fb

Browse files
committed
gh-153569: simplify formatted-string state
1 parent 8fbcd37 commit b0926fb

11 files changed

Lines changed: 308 additions & 361 deletions

File tree

Lib/test/test_fstring.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -663,7 +663,10 @@ def create_nested_fstring(n):
663663
prev = create_nested_fstring(n-1)
664664
return f'f"{{{prev}}}"'
665665

666-
raises_syntax_or_memory_error(create_nested_fstring(160))
666+
compile(create_nested_fstring(149), "<string>", "eval")
667+
with self.assertRaisesRegex(
668+
SyntaxError, "too many nested f-strings or t-strings"):
669+
compile(create_nested_fstring(150), "<string>", "eval")
667670
raises_syntax_or_memory_error("f'{" + "("*100 + "}'")
668671
raises_syntax_or_memory_error("f'{" + "("*1000 + "}'")
669672
raises_syntax_or_memory_error("f'{" + "("*10_000 + "}'")

Lib/test/test_repl.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -184,9 +184,8 @@ def read_until(marker, start=0):
184184

185185
@cpython_only
186186
def test_lexer_buffer_realloc_with_null_start(self):
187-
# gh-144759: NULL pointer arithmetic in the lexer when start and
188-
# multi_line_start are NULL (uninitialized in tok_mode_stack[0])
189-
# and the lexer buffer is reallocated while parsing long input.
187+
# gh-144759: NULL pointer arithmetic when the lexer buffer grows
188+
# while parsing long input.
190189
long_value = "a" * 2000
191190
user_input = dedent(f"""\
192191
x = f'{{{long_value!r}}}'

Lib/test/test_tokenize.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2574,6 +2574,12 @@ def test_degraded_fstring_format_spec(self):
25742574
("f-string: single '}' is not allowed", (1, 11)),
25752575
)
25762576

2577+
def test_carriage_return_after_debug_comment(self):
2578+
for prefix in ("f", "t"):
2579+
with self.subTest(prefix=prefix):
2580+
tokens = self._get_tokens(f"{prefix}'''{{x=# comment\r}}'''")
2581+
self.assertEqual(tokens[4].string, "# comment\r}")
2582+
25772583
def test_escaped_fstring_brace_has_a_position_gap(self):
25782584
tokens = self._get_tokens('f"a{{"', extra_tokens=True)
25792585
self.assertEqual(

Parser/action_helpers.c

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1001,14 +1001,33 @@ result_token_with_metadata(Parser *p, void *result, PyObject *metadata)
10011001
return res;
10021002
}
10031003

1004+
static char
1005+
formatted_string_prefix(const Parser *p)
1006+
{
1007+
int nested = 0;
1008+
for (int i = p->mark - 1; i >= 0; i--) {
1009+
int type = p->tokens[i]->type;
1010+
if (type == FSTRING_END || type == TSTRING_END) {
1011+
nested++;
1012+
}
1013+
else if (type == FSTRING_START || type == TSTRING_START) {
1014+
if (nested == 0) {
1015+
return type == TSTRING_START ? 't' : 'f';
1016+
}
1017+
nested--;
1018+
}
1019+
}
1020+
Py_UNREACHABLE();
1021+
}
1022+
10041023
ResultTokenWithMetadata *
10051024
_PyPegen_check_fstring_conversion(Parser *p, Token* conv_token, expr_ty conv)
10061025
{
10071026
if (conv_token->lineno != conv->lineno || conv_token->end_col_offset != conv->col_offset) {
10081027
return RAISE_SYNTAX_ERROR_KNOWN_RANGE(
10091028
conv_token, conv,
10101029
"%c-string: conversion type must come right after the exclamation mark",
1011-
_PyLexer_CurrentStringPrefix(p->tok)
1030+
formatted_string_prefix(p)
10121031
);
10131032
}
10141033

@@ -1017,7 +1036,7 @@ _PyPegen_check_fstring_conversion(Parser *p, Token* conv_token, expr_ty conv)
10171036
!(first == 's' || first == 'r' || first == 'a')) {
10181037
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(conv,
10191038
"%c-string: invalid conversion character %R: expected 's', 'r', or 'a'",
1020-
_PyLexer_CurrentStringPrefix(p->tok),
1039+
formatted_string_prefix(p),
10211040
conv->v.Name.id);
10221041
return NULL;
10231042
}
@@ -1379,7 +1398,7 @@ _get_resized_exprs(Parser *p, Token *a, asdl_expr_seq *raw_expressions,
13791398
asdl_expr_seq *values = item->v.JoinedStr.values;
13801399
if (asdl_seq_LEN(values) != 2) {
13811400
PyErr_Format(PyExc_SystemError,
1382-
_PyLexer_IsTStringKind(string_kind)
1401+
_PyLexer_IsTString(string_kind)
13831402
? "unexpected TemplateStr node without debug data in t-string at line %d"
13841403
: "unexpected JoinedStr node without debug data in f-string at line %d",
13851404
item->lineno);
@@ -1391,7 +1410,7 @@ _get_resized_exprs(Parser *p, Token *a, asdl_expr_seq *raw_expressions,
13911410
asdl_seq_SET(seq, index++, first);
13921411

13931412
expr_ty second = asdl_seq_GET(values, 1);
1394-
assert((_PyLexer_IsTStringKind(string_kind) &&
1413+
assert((_PyLexer_IsTString(string_kind) &&
13951414
second->kind == Interpolation_kind) ||
13961415
second->kind == FormattedValue_kind);
13971416
asdl_seq_SET(seq, index++, second);
@@ -1463,12 +1482,8 @@ expr_ty _PyPegen_decoded_constant_from_token(Parser* p, Token* tok) {
14631482
return NULL;
14641483
}
14651484

1466-
// Check if we're inside a raw f-string for format spec decoding
1467-
int is_raw = 0;
1468-
if (_PyLexer_InsideFString(p->tok)) {
1469-
tokenizer_mode *mode = _PyLexer_CurrentMode(p->tok);
1470-
is_raw = _PyLexer_IsRawString(mode);
1471-
}
1485+
const ftstring_state *state = _PyLexer_CurrentFTString(p->tok);
1486+
int is_raw = state != NULL && _PyLexer_IsRawString(state->kind);
14721487

14731488
PyObject* str = _PyPegen_decode_string(p, is_raw, bstr, bsize, tok);
14741489
if (str == NULL) {

Parser/lexer/lexer.c

Lines changed: 46 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -169,8 +169,11 @@ tok_continuation_line(struct tok_state *tok) {
169169

170170

171171
int
172-
_PyLexer_get_normal_mode(struct tok_state *tok, tokenizer_mode* current_tok, struct token *token)
172+
_PyLexer_get_normal(struct tok_state *tok, ftstring_state *current, struct token *token)
173173
{
174+
assert(current == NULL ||
175+
(current->mode == FTSTRING_MODE_EXPRESSION &&
176+
current->replacement_depth > 0));
174177
int c;
175178
int blankline, nonascii;
176179

@@ -333,13 +336,13 @@ _PyLexer_get_normal_mode(struct tok_state *tok, tokenizer_mode* current_tok, str
333336
c = tok_nextc(tok);
334337
}
335338

336-
if (_PyLexer_InsideFString(tok) && INSIDE_FSTRING_EXPR(current_tok)) {
339+
if (current != NULL) {
337340
const char *comment_end = tok->cur;
338341
if (c == '\n') {
339342
comment_end--;
340343
}
341344
if (_PyLexer_record_ftstring_comment(
342-
tok, tok->start, comment_end) < 0) {
345+
tok, current, tok->start, comment_end) < 0) {
343346
tok->done = E_NOMEM;
344347
return MAKE_TOKEN(ERRORTOKEN);
345348
}
@@ -545,12 +548,11 @@ _PyLexer_get_normal_mode(struct tok_state *tok, tokenizer_mode* current_tok, str
545548

546549
/* Punctuation character */
547550
int is_punctuation = (c == ':' || c == '}' || c == '!');
548-
if (is_punctuation && _PyLexer_InsideFString(tok) &&
549-
INSIDE_FSTRING_EXPR(current_tok)) {
550-
int cursor = current_tok->curly_bracket_depth - 1;
551-
int in_format_spec = current_tok->in_format_spec;
551+
if (is_punctuation && current != NULL) {
552+
int bracket_depth = _PyLexer_FTStringBracketDepth(tok, current);
553+
int cursor = bracket_depth - 1;
552554
int cursor_in_format_with_debug =
553-
cursor == 1 && (current_tok->in_debug || in_format_spec);
555+
cursor == 1 && current->debug_expr;
554556
int cursor_valid = cursor == 0 || cursor_in_format_with_debug;
555557
if (cursor_valid && c == '!') {
556558
int c2 = tok_nextc(tok);
@@ -560,17 +562,15 @@ _PyLexer_get_normal_mode(struct tok_state *tok, tokenizer_mode* current_tok, str
560562
tok_backup(tok, c2);
561563
}
562564
if (cursor_valid) {
563-
_PyLexer_finish_ftstring_expr(tok);
565+
_PyLexer_finish_ftstring_expr(tok, current);
564566
}
565567
if (cursor_valid &&
566-
_PyLexer_set_ftstring_expr_metadata(tok, token)) {
568+
_PyLexer_set_ftstring_expr_metadata(tok, current, token)) {
567569
return MAKE_TOKEN(ERRORTOKEN);
568570
}
569571

570-
if (c == ':' &&
571-
cursor == current_tok->curly_bracket_expr_start_depth) {
572-
current_tok->kind = TOK_FSTRING_MODE;
573-
current_tok->in_format_spec = 1;
572+
if (c == ':' && bracket_depth == current->replacement_depth) {
573+
current->mode = FTSTRING_MODE_FORMAT_SPEC;
574574
p_start = tok->start;
575575
p_end = tok->cur;
576576
return MAKE_TOKEN(_PyToken_OneChar(c));
@@ -609,18 +609,20 @@ _PyLexer_get_normal_mode(struct tok_state *tok, tokenizer_mode* current_tok, str
609609
tok->parenlinenostack[tok->level] = tok->lineno;
610610
tok->parencolstack[tok->level] = (int)(tok->start - tok->line_start);
611611
tok->level++;
612-
if (_PyLexer_InsideFString(tok)) {
613-
current_tok->curly_bracket_depth++;
614-
}
615612
break;
616613
case ')':
617614
case ']':
618615
case '}':
619-
if (_PyLexer_InsideFString(tok) &&
620-
!current_tok->curly_bracket_depth && c == '}') {
621-
return MAKE_TOKEN(_PyTokenizer_syntaxerror(tok,
622-
"%c-string: single '}' is not allowed",
623-
_PyLexer_CurrentStringPrefix(tok)));
616+
if (current != NULL &&
617+
_PyLexer_FTStringBracketDepth(tok, current) == 0) {
618+
if (c == '}') {
619+
return MAKE_TOKEN(_PyTokenizer_syntaxerror(tok,
620+
"%c-string: single '}' is not allowed",
621+
_PyLexer_StringPrefix(current->kind)));
622+
}
623+
return MAKE_TOKEN(_PyTokenizer_syntaxerror(
624+
tok, "%c-string: unmatched '%c'",
625+
_PyLexer_StringPrefix(current->kind), c));
624626
}
625627
if (!tok->tok_extra_tokens && !tok->level) {
626628
return MAKE_TOKEN(_PyTokenizer_syntaxerror(tok, "unmatched '%c'", c));
@@ -631,18 +633,15 @@ _PyLexer_get_normal_mode(struct tok_state *tok, tokenizer_mode* current_tok, str
631633
if (!tok->tok_extra_tokens && !((opening == '(' && c == ')') ||
632634
(opening == '[' && c == ']') ||
633635
(opening == '{' && c == '}'))) {
634-
/* If the opening bracket belongs to an f-string's expression
635-
part (e.g. f"{)}") and the closing bracket is an arbitrary
636-
nested expression, then instead of matching a different
637-
syntactical construct with it; we'll throw an unmatched
638-
parentheses error. */
639-
if (_PyLexer_InsideFString(tok) && opening == '{') {
640-
assert(current_tok->curly_bracket_depth >= 0);
641-
int previous_bracket = current_tok->curly_bracket_depth - 1;
642-
if (previous_bracket == current_tok->curly_bracket_expr_start_depth) {
636+
/* Do not match a closer against the brace that opened the
637+
* current replacement field. */
638+
if (current != NULL && opening == '{') {
639+
int bracket_depth =
640+
_PyLexer_FTStringBracketDepth(tok, current);
641+
if (bracket_depth == current->replacement_depth - 1) {
643642
return MAKE_TOKEN(_PyTokenizer_syntaxerror(tok,
644643
"%c-string: unmatched '%c'",
645-
_PyLexer_CurrentStringPrefix(tok), c));
644+
_PyLexer_StringPrefix(current->kind), c));
646645
}
647646
}
648647
if (tok->parenlinenostack[tok->level] != tok->lineno) {
@@ -660,18 +659,16 @@ _PyLexer_get_normal_mode(struct tok_state *tok, tokenizer_mode* current_tok, str
660659
}
661660
}
662661

663-
if (_PyLexer_InsideFString(tok)) {
664-
current_tok->curly_bracket_depth--;
665-
if (current_tok->curly_bracket_depth < 0) {
662+
if (current != NULL) {
663+
int bracket_depth = _PyLexer_FTStringBracketDepth(tok, current);
664+
if (bracket_depth < 0) {
666665
return MAKE_TOKEN(_PyTokenizer_syntaxerror(tok, "%c-string: unmatched '%c'",
667-
_PyLexer_CurrentStringPrefix(tok), c));
666+
_PyLexer_StringPrefix(current->kind), c));
668667
}
669-
if (c == '}' && current_tok->curly_bracket_depth ==
670-
current_tok->curly_bracket_expr_start_depth) {
671-
current_tok->curly_bracket_expr_start_depth--;
672-
current_tok->kind = TOK_FSTRING_MODE;
673-
current_tok->in_format_spec = 0;
674-
current_tok->in_debug = 0;
668+
if (c == '}' && bracket_depth == current->replacement_depth - 1) {
669+
current->replacement_depth--;
670+
current->mode = FTSTRING_MODE_MIDDLE;
671+
current->debug_expr = 0;
675672
}
676673
}
677674
break;
@@ -683,8 +680,9 @@ _PyLexer_get_normal_mode(struct tok_state *tok, tokenizer_mode* current_tok, str
683680
return MAKE_TOKEN(_PyTokenizer_syntaxerror(tok, "invalid non-printable character U+%04X", c));
684681
}
685682

686-
if (c == '=' && INSIDE_FSTRING_EXPR_AT_TOP(current_tok)) {
687-
current_tok->in_debug = 1;
683+
if (c == '=' && current != NULL &&
684+
_PyLexer_FTStringBracketDepth(tok, current) == current->replacement_depth) {
685+
current->debug_expr = 1;
688686
}
689687

690688
/* Punctuation character */
@@ -697,12 +695,11 @@ _PyLexer_get_normal_mode(struct tok_state *tok, tokenizer_mode* current_tok, str
697695
static int
698696
tok_get(struct tok_state *tok, struct token *token)
699697
{
700-
tokenizer_mode *current_tok = _PyLexer_CurrentMode(tok);
701-
if (current_tok->kind == TOK_REGULAR_MODE) {
702-
return _PyLexer_get_normal_mode(tok, current_tok, token);
703-
} else {
704-
return _PyLexer_get_fstring_mode(tok, current_tok, token);
698+
ftstring_state *current = _PyLexer_CurrentFTString(tok);
699+
if (current == NULL || current->mode == FTSTRING_MODE_EXPRESSION) {
700+
return _PyLexer_get_normal(tok, current, token);
705701
}
702+
return _PyLexer_get_ftstring(tok, current, token);
706703
}
707704

708705
int

Parser/lexer/lexer_internal.h

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,10 @@ enum string_prefix_flags {
2525
STRING_PREFIX_T = 1 << 4,
2626
};
2727

28-
#define FTSTRING_MIDDLE(mode) \
29-
(_PyLexer_IsTString(mode) ? TSTRING_MIDDLE : FSTRING_MIDDLE)
30-
#define FTSTRING_END(mode) \
31-
(_PyLexer_IsTString(mode) ? TSTRING_END : FSTRING_END)
28+
#define FTSTRING_MIDDLE(state) \
29+
(_PyLexer_IsTString((state)->kind) ? TSTRING_MIDDLE : FSTRING_MIDDLE)
30+
#define FTSTRING_END(state) \
31+
(_PyLexer_IsTString((state)->kind) ? TSTRING_END : FSTRING_END)
3232
#define tok_nextc _PyLexer_nextc
3333
#define tok_backup _PyLexer_backup
3434

@@ -41,16 +41,16 @@ tok_failed(const struct tok_state *tok)
4141

4242
int _PyLexer_nextc(struct tok_state *);
4343
void _PyLexer_backup(struct tok_state *, int);
44-
void _PyLexer_begin_ftstring_expr(struct tok_state *);
45-
void _PyLexer_finish_ftstring_expr(struct tok_state *);
44+
void _PyLexer_finish_ftstring_expr(struct tok_state *, ftstring_state *);
4645
int _PyLexer_record_ftstring_comment(
47-
struct tok_state *, const char *, const char *);
48-
int _PyLexer_set_ftstring_expr_metadata(struct tok_state *, struct token *);
46+
struct tok_state *, ftstring_state *, const char *, const char *);
47+
int _PyLexer_set_ftstring_expr_metadata(
48+
struct tok_state *, const ftstring_state *, struct token *);
4949
int _PyLexer_scan_prefixed_string(
5050
struct tok_state *, struct token *, int, unsigned int);
5151
int _PyLexer_scan_number(struct tok_state *, struct token *, int, int);
5252
int _PyLexer_scan_string(struct tok_state *, struct token *, int);
53-
int _PyLexer_get_normal_mode(struct tok_state *, tokenizer_mode *, struct token *);
54-
int _PyLexer_get_fstring_mode(struct tok_state *, tokenizer_mode *, struct token *);
53+
int _PyLexer_get_normal(struct tok_state *, ftstring_state *, struct token *);
54+
int _PyLexer_get_ftstring(struct tok_state *, ftstring_state *, struct token *);
5555

5656
#endif

0 commit comments

Comments
 (0)