From 296ecc6728c4e0fb2cdb6682428dc6a439f902e7 Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Sat, 26 Aug 2017 11:10:04 +0200 Subject: [PATCH] Add 'f-string: expressions nested too deeply' issue. --- parso/python/errors.py | 35 +++++++++++++++++++++++++---------- parso/python/fstring.py | 3 ++- test/failing_examples.py | 1 + 3 files changed, 28 insertions(+), 11 deletions(-) diff --git a/parso/python/errors.py b/parso/python/errors.py index 0efea5f..afe65b3 100644 --- a/parso/python/errors.py +++ b/parso/python/errors.py @@ -841,13 +841,12 @@ class _FStringRule(SyntaxRule): _fstring_grammar = None message_empty = "f-string: empty expression not allowed" # f'{}' "f-string: single '}' is not allowed" # f'}' - "f-string: expressions nested too deeply" # f'{1:{5:{3}}}' + message_nested = "f-string: expressions nested too deeply" message_backslash = "f-string expression part cannot include a backslash" # f'{"\"}' or f'{"\\"}' message_comment = "f-string expression part cannot include '#'" # f'{#}' "f-string: unterminated string" # f'{"}' "f-string: mismatched '(', '{', or '['" message_conversion = "f-string: invalid conversion character: expected 's', 'r', or 'a'" - "f-string: unexpected end of string" # Doesn't really happen?! "f-string: expecting '}'" # f'{' @classmethod @@ -865,15 +864,9 @@ class _FStringRule(SyntaxRule): parsed = self._load_grammar().parse_leaf(fstring) for child in parsed.children: if child.type == 'expression': - for c in child.children: - if c.type == 'python_expr': - self._check_expression(c) - elif c.type == 'conversion': - if c.value not in ('s', 'r', 'a'): - self.add_issue(c, message=self.message_conversion) - self._check_expression(child.children[1]) + self._check_expression(child) - def _check_expression(self, python_expr): + def _check_python_expr(self, python_expr): value = python_expr.value if '\\' in value: self.add_issue(python_expr, message=self.message_backslash) @@ -888,6 +881,28 @@ class _FStringRule(SyntaxRule): if parsed_expr.type == 'endmarker': self.add_issue(python_expr, message=self.message_empty) + def _check_format_spec(self, format_spec): + for expression in format_spec.children[1:]: + nested_format_spec = expression.children[-2] + if nested_format_spec.type == 'format_spec': + if len(nested_format_spec.children) > 1: + self.add_issue( + nested_format_spec.children[1], + message=self.message_nested + ) + + self._check_expression(expression) + + def _check_expression(self, expression): + for c in expression.children: + if c.type == 'python_expr': + self._check_python_expr(c) + elif c.type == 'conversion': + if c.value not in ('s', 'r', 'a'): + self.add_issue(c, message=self.message_conversion) + elif c.type == 'format_spec': + self._check_format_spec(c) + class _CheckAssignmentRule(SyntaxRule): def _check_assignment(self, node, is_deletion=False): diff --git a/parso/python/fstring.py b/parso/python/fstring.py index 50ee09c..8487a2a 100644 --- a/parso/python/fstring.py +++ b/parso/python/fstring.py @@ -38,7 +38,8 @@ class TokenNamespace: START_SYMBOL = 'fstring' GRAMMAR = """ fstring: expression* ENDMARKER -expression: '{' PYTHON_EXPR [ '!' CONVERSION ] [ ':' expression* ] '}' +format_spec: ':' expression* +expression: '{' PYTHON_EXPR [ '!' CONVERSION ] [ format_spec ] '}' """ _prefix = r'((?:[^{}]+)*)' diff --git a/test/failing_examples.py b/test/failing_examples.py index d68856c..9145289 100644 --- a/test/failing_examples.py +++ b/test/failing_examples.py @@ -145,6 +145,7 @@ FAILING_EXAMPLES = [ 'f"{\'\\\'}"', 'f"{#}"', "f'{1!b}'", + "f'{1:{5:{3}}}'", ] GLOBAL_NONLOCAL_ERROR = [