summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeppe Dakin <jeppe_dakin@hotmail.com>2021-01-17 12:54:52 +0100
committerGitHub <noreply@github.com>2021-01-17 12:54:52 +0100
commit19d2096185de3282345eab1da611e56a26bcaec2 (patch)
tree1e0272fe3b9502269114deefa49e7fcc318e0d39
parent7f48240569973617d7c7e8ece12087eb6149ad23 (diff)
downloadpygments-git-19d2096185de3282345eab1da611e56a26bcaec2.tar.gz
Fix for lexing Python raw f-strings with backslashes (#1683)
* introduce and apply rfstringescape * add unit test for raw f-strings * add further tests * fix comment
-rw-r--r--pygments/lexers/python.py17
-rw-r--r--tests/test_python.py48
2 files changed, 60 insertions, 5 deletions
diff --git a/pygments/lexers/python.py b/pygments/lexers/python.py
index f995d36f..8a9b7239 100644
--- a/pygments/lexers/python.py
+++ b/pygments/lexers/python.py
@@ -122,13 +122,17 @@ class PythonLexer(RegexLexer):
'expr': [
# raw f-strings
('(?i)(rf|fr)(""")',
- bygroups(String.Affix, String.Double), 'tdqf'),
+ bygroups(String.Affix, String.Double),
+ combined('rfstringescape', 'tdqf')),
("(?i)(rf|fr)(''')",
- bygroups(String.Affix, String.Single), 'tsqf'),
+ bygroups(String.Affix, String.Single),
+ combined('rfstringescape', 'tsqf')),
('(?i)(rf|fr)(")',
- bygroups(String.Affix, String.Double), 'dqf'),
+ bygroups(String.Affix, String.Double),
+ combined('rfstringescape', 'dqf')),
("(?i)(rf|fr)(')",
- bygroups(String.Affix, String.Single), 'sqf'),
+ bygroups(String.Affix, String.Single),
+ combined('rfstringescape', 'sqf')),
# non-raw f-strings
('([fF])(""")', bygroups(String.Affix, String.Double),
combined('fstringescape', 'tdqf')),
@@ -316,9 +320,12 @@ class PythonLexer(RegexLexer):
(uni_name, Name.Namespace),
default('#pop'),
],
- 'fstringescape': [
+ 'rfstringescape': [
(r'\{\{', String.Escape),
(r'\}\}', String.Escape),
+ ],
+ 'fstringescape': [
+ include('rfstringescape'),
include('stringescape'),
],
'stringescape': [
diff --git a/tests/test_python.py b/tests/test_python.py
index 188044fa..ee36a331 100644
--- a/tests/test_python.py
+++ b/tests/test_python.py
@@ -840,3 +840,51 @@ def test_fstring(lexer3):
match = pattern.sub(lambda m: rep[m.group(0)], match)
tokens[i] = (token, match)
assert list(lexer3.get_tokens(fragment)) == tokens
+
+def test_raw_fstring(lexer3):
+ """
+ Tests that the lexer can parse raw f-strings
+ """
+ # Just raw
+ fragment = r'rf"m_\nu = x"'
+ tokens = [
+ (Token.Literal.String.Affix, 'rf'),
+ (Token.Literal.String.Double, '"'),
+ (Token.Literal.String.Double, 'm_'),
+ (Token.Literal.String.Double, '\\'),
+ (Token.Literal.String.Double, 'nu = x'),
+ (Token.Literal.String.Double, '"'),
+ (Token.Text, '\n')
+ ]
+ # Just f-string
+ fragment = r'f"m_\nu = {x}"'
+ tokens = [
+ (Token.Literal.String.Affix, 'f'),
+ (Token.Literal.String.Double, '"'),
+ (Token.Literal.String.Double, 'm_'),
+ (Token.Literal.String.Escape, '\\n'),
+ (Token.Literal.String.Double, 'u = '),
+ (Token.Literal.String.Interpol, '{'),
+ (Token.Name, 'x'),
+ (Token.Literal.String.Interpol, '}'),
+ (Token.Literal.String.Double, '"'),
+ (Token.Text, '\n'),
+ ]
+ # Raw behavior inside {{...}}
+ fragment = r'rf"m_{{\nu}} = {x}"'
+ tokens = [
+ (Token.Literal.String.Affix, 'rf'),
+ (Token.Literal.String.Double, '"'),
+ (Token.Literal.String.Double, 'm_'),
+ (Token.Literal.String.Escape, '{{'),
+ (Token.Literal.String.Double, '\\'),
+ (Token.Literal.String.Double, 'nu'),
+ (Token.Literal.String.Escape, '}}'),
+ (Token.Literal.String.Double, ' = '),
+ (Token.Literal.String.Interpol, '{'),
+ (Token.Name, 'x'),
+ (Token.Literal.String.Interpol, '}'),
+ (Token.Literal.String.Double, '"'),
+ (Token.Text, '\n'),
+ ]
+ assert list(lexer3.get_tokens(fragment)) == tokens