Skip to content

Commit

Permalink
Merge pull request #52 from isidentical/last-fstring-status
Browse files Browse the repository at this point in the history
  • Loading branch information
pablogsal authored Mar 28, 2023
2 parents 0762910 + 00f8bda commit a0edfb1
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 15 deletions.
31 changes: 21 additions & 10 deletions Lib/test/test_fstring.py
Original file line number Diff line number Diff line change
Expand Up @@ -481,31 +481,31 @@ def test_literal(self):
self.assertEqual(f' ', ' ')

def test_unterminated_string(self):
self.assertAllRaise(SyntaxError, 'f-string: unterminated string',
self.assertAllRaise(SyntaxError, 'unterminated string',
[r"""f'{"x'""",
r"""f'{"x}'""",
r"""f'{("x'""",
r"""f'{("x}'""",
])

def test_mismatched_parens(self):
self.assertAllRaise(SyntaxError, r"f-string: closing parenthesis '\}' "
self.assertAllRaise(SyntaxError, r"closing parenthesis '\}' "
r"does not match opening parenthesis '\('",
["f'{((}'",
])
self.assertAllRaise(SyntaxError, r"f-string: closing parenthesis '\)' "
self.assertAllRaise(SyntaxError, r"closing parenthesis '\)' "
r"does not match opening parenthesis '\['",
["f'{a[4)}'",
])
self.assertAllRaise(SyntaxError, r"f-string: closing parenthesis '\]' "
self.assertAllRaise(SyntaxError, r"closing parenthesis '\]' "
r"does not match opening parenthesis '\('",
["f'{a(4]}'",
])
self.assertAllRaise(SyntaxError, r"f-string: closing parenthesis '\}' "
self.assertAllRaise(SyntaxError, r"closing parenthesis '\}' "
r"does not match opening parenthesis '\['",
["f'{a[4}'",
])
self.assertAllRaise(SyntaxError, r"f-string: closing parenthesis '\}' "
self.assertAllRaise(SyntaxError, r"closing parenthesis '\}' "
r"does not match opening parenthesis '\('",
["f'{a(4}'",
])
Expand Down Expand Up @@ -573,7 +573,7 @@ def test_compile_time_concat(self):
self.assertEqual(f'' '' f'', '')
self.assertEqual(f'' '' f'' '', '')

self.assertAllRaise(SyntaxError, "f-string: expecting '}'",
self.assertAllRaise(SyntaxError, "expecting '}'",
["f'{3' f'}'", # can't concat to get a valid f-string
])

Expand Down Expand Up @@ -729,7 +729,7 @@ def test_parens_in_expressions(self):
# are added around it. But we shouldn't go from an invalid
# expression to a valid one. The added parens are just
# supposed to allow whitespace (including newlines).
self.assertAllRaise(SyntaxError, 'f-string: invalid syntax',
self.assertAllRaise(SyntaxError, 'invalid syntax',
["f'{,}'",
"f'{,}'", # this is (,), which is an error
])
Expand Down Expand Up @@ -786,7 +786,7 @@ def test_backslashes_in_string_part(self):
self.assertEqual(f'2\x203', '2 3')
self.assertEqual(f'\x203', ' 3')

with self.assertWarns(SyntaxWarning): # invalid escape sequence
with self.assertWarns(DeprecationWarning): # invalid escape sequence
value = eval(r"f'\{6*7}'")
self.assertEqual(value, '\\42')
self.assertEqual(f'\\{6*7}', '\\42')
Expand Down Expand Up @@ -1091,6 +1091,11 @@ def test_conversions(self):
self.assertEqual(f'{"a"!r}', "'a'")
self.assertEqual(f'{"a"!a}', "'a'")

# Conversions can have trailing whitespace after them since it
# does not provide any significance
self.assertEqual(f"{3!s }", "3")
self.assertEqual(f'{3.14!s :10.10}', '3.14 ')

# Not a conversion.
self.assertEqual(f'{"a!r"}', "a!r")

Expand All @@ -1109,12 +1114,18 @@ def test_conversions(self):
"f'{3!:}'",
])

for conv in 'g', 'A', '3', 'G', '!', ' s', 's ', ' s ', 'ä', 'ɐ', 'ª':
for conv in 'g', 'A', '3', 'G', '!', 'ä', 'ɐ', 'ª':
self.assertAllRaise(SyntaxError,
"f-string: invalid conversion character %r: "
"expected 's', 'r', or 'a'" % conv,
["f'{3!" + conv + "}'"])

for conv in ' s', ' s ':
self.assertAllRaise(SyntaxError,
"f-string: conversion type must come right after the"
" exclamanation mark",
["f'{3!" + conv + "}'"])

self.assertAllRaise(SyntaxError,
"f-string: invalid conversion character 'ss': "
"expected 's', 'r', or 'a'",
Expand Down
5 changes: 3 additions & 2 deletions Parser/action_helpers.c
Original file line number Diff line number Diff line change
Expand Up @@ -970,7 +970,7 @@ _PyPegen_check_fstring_conversion(Parser *p, Token* symbol, expr_ty conv) {
if (symbol->lineno != conv->lineno || symbol->end_col_offset != conv->col_offset) {
RAISE_SYNTAX_ERROR_KNOWN_RANGE(
symbol, conv,
"conversion type must come right after the exclamanation mark"
"f-string: conversion type must come right after the exclamanation mark"
);
return 1;
}
Expand Down Expand Up @@ -1391,7 +1391,8 @@ expr_ty _PyPegen_formatted_value(Parser *p, expr_ty expression, Token *debug, ex
if (PyUnicode_GET_LENGTH(conversion->v.Name.id) > 1 ||
!(first == 's' || first == 'r' || first == 'a')) {
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(conversion,
"f-string: invalid conversion character: expected 's', 'r', or 'a'");
"f-string: invalid conversion character %R: expected 's', 'r', or 'a'",
conversion->v.Name.id);
return NULL;
}

Expand Down
23 changes: 20 additions & 3 deletions Parser/tokenizer.c
Original file line number Diff line number Diff line change
Expand Up @@ -2378,6 +2378,9 @@ tok_get_normal_mode(struct tok_state *tok, tokenizer_mode* current_tok, struct t
case ']':
case '}':
if (!tok->level) {
if (tok->tok_mode_stack_index > 0 && !current_tok->bracket_stack && c == '}') {
return MAKE_TOKEN(syntaxerror(tok, "f-string: single '}' is not allowed"));
}
return MAKE_TOKEN(syntaxerror(tok, "unmatched '%c'", c));
}
tok->level--;
Expand All @@ -2386,6 +2389,18 @@ tok_get_normal_mode(struct tok_state *tok, tokenizer_mode* current_tok, struct t
(opening == '[' && c == ']') ||
(opening == '{' && c == '}')))
{
/* If the opening bracket belongs to an f-string's expression
part (e.g. f"{)}") and the closing bracket is an arbitrary
nested expression, then instead of matching a different
syntactical construct with it; we'll throw an unmatched
parentheses error. */
if (tok->tok_mode_stack_index > 0 && opening == '{') {
assert(current_tok->bracket_stack >= 0);
int previous_bracket = current_tok->bracket_stack - 1;
if (previous_bracket == current_tok->bracket_mark[current_tok->bracket_mark_index]) {
return MAKE_TOKEN(syntaxerror(tok, "f-string: unmatched '%c'", c));
}
}
if (tok->parenlinenostack[tok->level] != tok->lineno) {
return MAKE_TOKEN(syntaxerror(tok,
"closing parenthesis '%c' does not match "
Expand Down Expand Up @@ -2427,6 +2442,9 @@ tok_get_fstring_mode(struct tok_state *tok, tokenizer_mode* current_tok, struct
{
const char *p_start = NULL;
const char *p_end = NULL;
int end_quote_size = 0;
int unicode_escape = 0;

tok->start = tok->cur;
tok->first_lineno = tok->lineno;
tok->starting_col_offset = tok->col_offset;
Expand Down Expand Up @@ -2467,9 +2485,8 @@ tok_get_fstring_mode(struct tok_state *tok, tokenizer_mode* current_tok, struct
tok->tok_mode_stack_index--;
return MAKE_TOKEN(FSTRING_END);

f_string_middle:
int end_quote_size = 0;
int unicode_escape = 0;
f_string_middle:

while (end_quote_size != current_tok->f_string_quote_size) {
int c = tok_nextc(tok);
if (c == EOF || (current_tok->f_string_quote_size == 1 && c == '\n')) {
Expand Down

0 comments on commit a0edfb1

Please sign in to comment.