From a69c2b6259e80862c56f7ce13df052b6101ecac7 Mon Sep 17 00:00:00 2001
From: Michael Schmidt
Date: Sun, 2 Dec 2018 16:48:53 +0100
Subject: [PATCH] Improvements to Python F-strings and string prefixes (#1642)
This PR adds support for [string interpolation](https://www.python.org/dev/peps/pep-0498/) (aka. f-strings) and makes the [string prefixes](https://docs.python.org/3/reference/lexical_analysis.html#string-and-bytes-literals) part of the string.
Resolves #1636.
### Known issues
Assumes that strings inside the interpolation expression are 'nice'. So strings
with unfortunate numbers of curley braces will cause incorrect highlighting: E.g.: `f"{'}'}"`.
---
components/prism-python.js | 29 +++-
components/prism-python.min.js | 2 +-
examples/prism-python.html | 7 +-
.../python/string-interpolation_feature.test | 147 ++++++++++++++++++
tests/languages/python/string_feature.test | 12 +-
5 files changed, 188 insertions(+), 9 deletions(-)
create mode 100644 tests/languages/python/string-interpolation_feature.test
diff --git a/components/prism-python.js b/components/prism-python.js
index b326a5ceb2..ea1a51545a 100644
--- a/components/prism-python.js
+++ b/components/prism-python.js
@@ -3,13 +3,36 @@ Prism.languages.python = {
pattern: /(^|[^\\])#.*/,
lookbehind: true
},
+ 'string-interpolation': {
+ pattern: /(?:f|rf|fr)(?:("""|''')[\s\S]+?\1|("|')(?:\\.|(?!\2)[^\\\r\n])*\2)/i,
+ greedy: true,
+ inside: {
+ 'interpolation': {
+ // "{" "}"
+ pattern: /((?:^|[^{])(?:{{)*){(?!{)(?:[^{}]|{(?!{)(?:[^{}]|{(?!{)(?:[^{}])+})+})+}/,
+ lookbehind: true,
+ inside: {
+ 'format-spec': {
+ pattern: /(:)[^:(){}]+(?=}$)/,
+ lookbehind: true
+ },
+ 'conversion-option': {
+ pattern: /![sra](?=[:}]$)/,
+ alias: 'punctuation'
+ },
+ rest: null
+ }
+ },
+ 'string': /[\s\S]+/
+ }
+ },
'triple-quoted-string': {
- pattern: /("""|''')[\s\S]+?\1/,
+ pattern: /(?:[rub]|rb|br)?("""|''')[\s\S]+?\1/i,
greedy: true,
alias: 'string'
},
'string': {
- pattern: /("|')(?:\\.|(?!\1)[^\\\r\n])*\1/,
+ pattern: /(?:[rub]|rb|br)?("|')(?:\\.|(?!\1)[^\\\r\n])*\1/i,
greedy: true
},
'function': {
@@ -35,3 +58,5 @@ Prism.languages.python = {
'operator': /[-+%=]=?|!=|\*\*?=?|\/\/?=?|<[<=>]?|>[=>]?|[&|^~]/,
'punctuation': /[{}[\];(),.:]/
};
+
+Prism.languages.python['string-interpolation'].inside['interpolation'].inside.rest = Prism.languages.python;
diff --git a/components/prism-python.min.js b/components/prism-python.min.js
index 8956dae5b4..7f9ee42b2d 100644
--- a/components/prism-python.min.js
+++ b/components/prism-python.min.js
@@ -1 +1 @@
-Prism.languages.python={comment:{pattern:/(^|[^\\])#.*/,lookbehind:!0},"triple-quoted-string":{pattern:/("""|''')[\s\S]+?\1/,greedy:!0,alias:"string"},string:{pattern:/("|')(?:\\.|(?!\1)[^\\\r\n])*\1/,greedy:!0},"function":{pattern:/((?:^|\s)def[ \t]+)[a-zA-Z_]\w*(?=\s*\()/g,lookbehind:!0},"class-name":{pattern:/(\bclass\s+)\w+/i,lookbehind:!0},decorator:{pattern:/(^\s*)@\w+(?:\.\w+)*/i,lookbehind:!0,alias:["annotation","punctuation"],inside:{punctuation:/\./}},keyword:/\b(?:and|as|assert|async|await|break|class|continue|def|del|elif|else|except|exec|finally|for|from|global|if|import|in|is|lambda|nonlocal|not|or|pass|print|raise|return|try|while|with|yield)\b/,builtin:/\b(?:__import__|abs|all|any|apply|ascii|basestring|bin|bool|buffer|bytearray|bytes|callable|chr|classmethod|cmp|coerce|compile|complex|delattr|dict|dir|divmod|enumerate|eval|execfile|file|filter|float|format|frozenset|getattr|globals|hasattr|hash|help|hex|id|input|int|intern|isinstance|issubclass|iter|len|list|locals|long|map|max|memoryview|min|next|object|oct|open|ord|pow|property|range|raw_input|reduce|reload|repr|reversed|round|set|setattr|slice|sorted|staticmethod|str|sum|super|tuple|type|unichr|unicode|vars|xrange|zip)\b/,"boolean":/\b(?:True|False|None)\b/,number:/(?:\b(?=\d)|\B(?=\.))(?:0[bo])?(?:(?:\d|0x[\da-f])[\da-f]*\.?\d*|\.\d+)(?:e[+-]?\d+)?j?\b/i,operator:/[-+%=]=?|!=|\*\*?=?|\/\/?=?|<[<=>]?|>[=>]?|[&|^~]/,punctuation:/[{}[\];(),.:]/};
\ No newline at end of file
+Prism.languages.python={comment:{pattern:/(^|[^\\])#.*/,lookbehind:!0},"string-interpolation":{pattern:/(?:f|rf|fr)(?:("""|''')[\s\S]+?\1|("|')(?:\\.|(?!\2)[^\\\r\n])*\2)/i,greedy:!0,inside:{interpolation:{pattern:/((?:^|[^{])(?:{{)*){(?!{)(?:[^{}]|{(?!{)(?:[^{}]|{(?!{)(?:[^{}])+})+})+}/,lookbehind:!0,inside:{"format-spec":{pattern:/(:)[^:(){}]+(?=}$)/,lookbehind:!0},"conversion-option":{pattern:/![sra](?=[:}]$)/,alias:"punctuation"},rest:null}},string:/[\s\S]+/}},"triple-quoted-string":{pattern:/(?:[rub]|rb|br)?("""|''')[\s\S]+?\1/i,greedy:!0,alias:"string"},string:{pattern:/(?:[rub]|rb|br)?("|')(?:\\.|(?!\1)[^\\\r\n])*\1/i,greedy:!0},"function":{pattern:/((?:^|\s)def[ \t]+)[a-zA-Z_]\w*(?=\s*\()/g,lookbehind:!0},"class-name":{pattern:/(\bclass\s+)\w+/i,lookbehind:!0},decorator:{pattern:/(^\s*)@\w+(?:\.\w+)*/i,lookbehind:!0,alias:["annotation","punctuation"],inside:{punctuation:/\./}},keyword:/\b(?:and|as|assert|async|await|break|class|continue|def|del|elif|else|except|exec|finally|for|from|global|if|import|in|is|lambda|nonlocal|not|or|pass|print|raise|return|try|while|with|yield)\b/,builtin:/\b(?:__import__|abs|all|any|apply|ascii|basestring|bin|bool|buffer|bytearray|bytes|callable|chr|classmethod|cmp|coerce|compile|complex|delattr|dict|dir|divmod|enumerate|eval|execfile|file|filter|float|format|frozenset|getattr|globals|hasattr|hash|help|hex|id|input|int|intern|isinstance|issubclass|iter|len|list|locals|long|map|max|memoryview|min|next|object|oct|open|ord|pow|property|range|raw_input|reduce|reload|repr|reversed|round|set|setattr|slice|sorted|staticmethod|str|sum|super|tuple|type|unichr|unicode|vars|xrange|zip)\b/,"boolean":/\b(?:True|False|None)\b/,number:/(?:\b(?=\d)|\B(?=\.))(?:0[bo])?(?:(?:\d|0x[\da-f])[\da-f]*\.?\d*|\.\d+)(?:e[+-]?\d+)?j?\b/i,operator:/[-+%=]=?|!=|\*\*?=?|\/\/?=?|<[<=>]?|>[=>]?|[&|^~]/,punctuation:/[{}[\];(),.:]/},Prism.languages.python["string-interpolation"].inside.interpolation.inside.rest=Prism.languages.python;
\ No newline at end of file
diff --git a/examples/prism-python.html b/examples/prism-python.html
index ca7a397e99..ce6efc00fb 100644
--- a/examples/prism-python.html
+++ b/examples/prism-python.html
@@ -57,8 +57,5 @@ Known failures
If a failure is listed here, it doesn’t mean it will never be fixed. This is more of a “known bugs” list, just with a certain type of bug.
-Triple-quoted strings with what look like strings inside
-def antique(string):
- """Replace anachronistic Latin "j" with "i"."""
- return string.replace("j", "i").replace("J", "I")
-
+Interpolation expressions containing strings with {
or }
+f"{'}'}"
diff --git a/tests/languages/python/string-interpolation_feature.test b/tests/languages/python/string-interpolation_feature.test
new file mode 100644
index 0000000000..789cb83243
--- /dev/null
+++ b/tests/languages/python/string-interpolation_feature.test
@@ -0,0 +1,147 @@
+f'The value is {value}.'
+
+f"The value is {'4'}."
+
+f'input={value!s:#06x}'
+
+f'{{{4*10}}}'
+
+fr'x={4*10}\n'
+
+f'''{x
++1}'''
+
+f'mapping is { {a:b for (a, b) in ((1, 2), (3, 4))} }'
+
+f'{(lambda x: x*2)(3)}'
+
+----------------------------------------------------
+
+[
+ ["string-interpolation", [
+ ["string", "f'The value is "],
+ ["interpolation", [
+ ["punctuation", "{"],
+ "value",
+ ["punctuation", "}"]
+ ]],
+ ["string", ".'"]
+ ]],
+
+ ["string-interpolation", [
+ ["string", "f\"The value is "],
+ ["interpolation", [
+ ["punctuation", "{"],
+ ["string", "'4'"],
+ ["punctuation", "}"]
+ ]],
+ ["string", ".\""]
+ ]],
+
+ ["string-interpolation", [
+ ["string", "f'input="],
+ ["interpolation", [
+ ["punctuation", "{"],
+ "value",
+ ["conversion-option", "!s"],
+ ["punctuation", ":"],
+ ["format-spec", "#06x"],
+ ["punctuation", "}"]
+ ]],
+ ["string", "'"]
+ ]],
+
+ ["string-interpolation", [
+ ["string", "f'{{"],
+ ["interpolation", [
+ ["punctuation", "{"],
+ ["number", "4"],
+ ["operator", "*"],
+ ["number", "10"],
+ ["punctuation", "}"]
+ ]],
+ ["string", "}}'"]
+ ]],
+
+ ["string-interpolation", [
+ ["string", "fr'x="],
+ ["interpolation", [
+ ["punctuation", "{"],
+ ["number", "4"],
+ ["operator", "*"],
+ ["number", "10"],
+ ["punctuation", "}"]
+ ]],
+ ["string", "\\n'"]
+ ]],
+
+ ["string-interpolation", [
+ ["string", "f'''"],
+ ["interpolation", [
+ ["punctuation", "{"],
+ "x\r\n",
+ ["operator", "+"],
+ ["number", "1"],
+ ["punctuation", "}"]
+ ]],
+ ["string", "'''"]
+ ]],
+
+ ["string-interpolation", [
+ ["string", "f'mapping is "],
+ ["interpolation", [
+ ["punctuation", "{"],
+ ["punctuation", "{"],
+ "a",
+ ["punctuation", ":"],
+ "b ",
+ ["keyword", "for"],
+ ["punctuation", "("],
+ "a",
+ ["punctuation", ","],
+ " b",
+ ["punctuation", ")"],
+ ["keyword", "in"],
+ ["punctuation", "("],
+ ["punctuation", "("],
+ ["number", "1"],
+ ["punctuation", ","],
+ ["number", "2"],
+ ["punctuation", ")"],
+ ["punctuation", ","],
+ ["punctuation", "("],
+ ["number", "3"],
+ ["punctuation", ","],
+ ["number", "4"],
+ ["punctuation", ")"],
+ ["punctuation", ")"],
+ ["punctuation", "}"],
+ ["punctuation", "}"]
+ ]],
+ ["string", "'"]
+ ]],
+
+ ["string-interpolation", [
+ ["string", "f'"],
+ ["interpolation", [
+ ["punctuation", "{"],
+ ["punctuation", "("],
+ ["keyword", "lambda"],
+ " x",
+ ["punctuation", ":"],
+ " x",
+ ["operator", "*"],
+ ["number", "2"],
+ ["punctuation", ")"],
+ ["punctuation", "("],
+ ["number", "3"],
+ ["punctuation", ")"],
+ ["punctuation", "}"]
+ ]],
+ ["string", "'"]
+ ]]
+]
+
+----------------------------------------------------
+
+Checks for string interpolation.
\ No newline at end of file
diff --git a/tests/languages/python/string_feature.test b/tests/languages/python/string_feature.test
index 9e21c4d340..0bb511985a 100644
--- a/tests/languages/python/string_feature.test
+++ b/tests/languages/python/string_feature.test
@@ -4,6 +4,11 @@
'fo\'obar'
"fo\" # comment obar"
+r"\n"
+b'foo'
+rb"foo\n"
+u"foo"
+
----------------------------------------------------
[
@@ -11,7 +16,12 @@
["string", "\"fo\\\"obar\""],
["string", "''"],
["string", "'fo\\'obar'"],
- ["string", "\"fo\\\" # comment obar\""]
+ ["string", "\"fo\\\" # comment obar\""],
+
+ ["string", "r\"\\n\""],
+ ["string", "b'foo'"],
+ ["string", "rb\"foo\\n\""],
+ ["string", "u\"foo\""]
]
----------------------------------------------------