We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Failing to parse single-quote f-string with backslash inside it
**Describe the bug**

The formatter is failing to parse single quote f-string as it cannot include a backslash inside it.

**To Reproduce**

1. Take this test.py containing:
```python
b = "example1"
sts = "example2"
f"\"{b}\"{' ' * (long-len(b)+1)}: \"{sts}\",\n"
```
2. Run black without arguments.
3. See error -->
```
  File "/var/task/black/__init__.py", line 986, in assert_equivalent
    dst_ast = parse_ast(dst)
  File "/var/task/black/parsing.py", line 125, in parse_ast
    raise SyntaxError(
f'"{b}"{\' \' * (long-len(b)+1)}: "{sts}",\n'
```

**Expected behavior**
The formatter is expected to throw the said error and fail to format the said file.

**Environment (please complete the following information):**

- Version: [main]
- OS and Python version: [Linux/Python 3.9.5]

**Does this bug also happen on main?**
Yes


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/black/strings.py`
Content:
```
1 """
2 Simple formatting on strings. Further string formatting code is in trans.py.
3 """
4 
5 import regex as re
6 import sys
7 from typing import List, Pattern
8 
9 
10 STRING_PREFIX_CHARS = "furbFURB"  # All possible string prefix characters.
11 
12 
13 def sub_twice(regex: Pattern[str], replacement: str, original: str) -> str:
14     """Replace `regex` with `replacement` twice on `original`.
15 
16     This is used by string normalization to perform replaces on
17     overlapping matches.
18     """
19     return regex.sub(replacement, regex.sub(replacement, original))
20 
21 
22 def has_triple_quotes(string: str) -> bool:
23     """
24     Returns:
25         True iff @string starts with three quotation characters.
26     """
27     raw_string = string.lstrip(STRING_PREFIX_CHARS)
28     return raw_string[:3] in {'"""', "'''"}
29 
30 
31 def lines_with_leading_tabs_expanded(s: str) -> List[str]:
32     """
33     Splits string into lines and expands only leading tabs (following the normal
34     Python rules)
35     """
36     lines = []
37     for line in s.splitlines():
38         # Find the index of the first non-whitespace character after a string of
39         # whitespace that includes at least one tab
40         match = re.match(r"\s*\t+\s*(\S)", line)
41         if match:
42             first_non_whitespace_idx = match.start(1)
43 
44             lines.append(
45                 line[:first_non_whitespace_idx].expandtabs()
46                 + line[first_non_whitespace_idx:]
47             )
48         else:
49             lines.append(line)
50     return lines
51 
52 
53 def fix_docstring(docstring: str, prefix: str) -> str:
54     # https://www.python.org/dev/peps/pep-0257/#handling-docstring-indentation
55     if not docstring:
56         return ""
57     lines = lines_with_leading_tabs_expanded(docstring)
58     # Determine minimum indentation (first line doesn't count):
59     indent = sys.maxsize
60     for line in lines[1:]:
61         stripped = line.lstrip()
62         if stripped:
63             indent = min(indent, len(line) - len(stripped))
64     # Remove indentation (first line is special):
65     trimmed = [lines[0].strip()]
66     if indent < sys.maxsize:
67         last_line_idx = len(lines) - 2
68         for i, line in enumerate(lines[1:]):
69             stripped_line = line[indent:].rstrip()
70             if stripped_line or i == last_line_idx:
71                 trimmed.append(prefix + stripped_line)
72             else:
73                 trimmed.append("")
74     return "\n".join(trimmed)
75 
76 
77 def get_string_prefix(string: str) -> str:
78     """
79     Pre-conditions:
80         * assert_is_leaf_string(@string)
81 
82     Returns:
83         @string's prefix (e.g. '', 'r', 'f', or 'rf').
84     """
85     assert_is_leaf_string(string)
86 
87     prefix = ""
88     prefix_idx = 0
89     while string[prefix_idx] in STRING_PREFIX_CHARS:
90         prefix += string[prefix_idx]
91         prefix_idx += 1
92 
93     return prefix
94 
95 
96 def assert_is_leaf_string(string: str) -> None:
97     """
98     Checks the pre-condition that @string has the format that you would expect
99     of `leaf.value` where `leaf` is some Leaf such that `leaf.type ==
100     token.STRING`. A more precise description of the pre-conditions that are
101     checked are listed below.
102 
103     Pre-conditions:
104         * @string starts with either ', ", <prefix>', or <prefix>" where
105         `set(<prefix>)` is some subset of `set(STRING_PREFIX_CHARS)`.
106         * @string ends with a quote character (' or ").
107 
108     Raises:
109         AssertionError(...) if the pre-conditions listed above are not
110         satisfied.
111     """
112     dquote_idx = string.find('"')
113     squote_idx = string.find("'")
114     if -1 in [dquote_idx, squote_idx]:
115         quote_idx = max(dquote_idx, squote_idx)
116     else:
117         quote_idx = min(squote_idx, dquote_idx)
118 
119     assert (
120         0 <= quote_idx < len(string) - 1
121     ), f"{string!r} is missing a starting quote character (' or \")."
122     assert string[-1] in (
123         "'",
124         '"',
125     ), f"{string!r} is missing an ending quote character (' or \")."
126     assert set(string[:quote_idx]).issubset(
127         set(STRING_PREFIX_CHARS)
128     ), f"{set(string[:quote_idx])} is NOT a subset of {set(STRING_PREFIX_CHARS)}."
129 
130 
131 def normalize_string_prefix(s: str, remove_u_prefix: bool = False) -> str:
132     """Make all string prefixes lowercase.
133 
134     If remove_u_prefix is given, also removes any u prefix from the string.
135     """
136     match = re.match(r"^([" + STRING_PREFIX_CHARS + r"]*)(.*)$", s, re.DOTALL)
137     assert match is not None, f"failed to match string {s!r}"
138     orig_prefix = match.group(1)
139     new_prefix = orig_prefix.replace("F", "f").replace("B", "b").replace("U", "u")
140     if remove_u_prefix:
141         new_prefix = new_prefix.replace("u", "")
142     return f"{new_prefix}{match.group(2)}"
143 
144 
145 def normalize_string_quotes(s: str) -> str:
146     """Prefer double quotes but only if it doesn't cause more escaping.
147 
148     Adds or removes backslashes as appropriate. Doesn't parse and fix
149     strings nested in f-strings.
150     """
151     value = s.lstrip(STRING_PREFIX_CHARS)
152     if value[:3] == '"""':
153         return s
154 
155     elif value[:3] == "'''":
156         orig_quote = "'''"
157         new_quote = '"""'
158     elif value[0] == '"':
159         orig_quote = '"'
160         new_quote = "'"
161     else:
162         orig_quote = "'"
163         new_quote = '"'
164     first_quote_pos = s.find(orig_quote)
165     if first_quote_pos == -1:
166         return s  # There's an internal error
167 
168     prefix = s[:first_quote_pos]
169     unescaped_new_quote = re.compile(rf"(([^\\]|^)(\\\\)*){new_quote}")
170     escaped_new_quote = re.compile(rf"([^\\]|^)\\((?:\\\\)*){new_quote}")
171     escaped_orig_quote = re.compile(rf"([^\\]|^)\\((?:\\\\)*){orig_quote}")
172     body = s[first_quote_pos + len(orig_quote) : -len(orig_quote)]
173     if "r" in prefix.casefold():
174         if unescaped_new_quote.search(body):
175             # There's at least one unescaped new_quote in this raw string
176             # so converting is impossible
177             return s
178 
179         # Do not introduce or remove backslashes in raw strings
180         new_body = body
181     else:
182         # remove unnecessary escapes
183         new_body = sub_twice(escaped_new_quote, rf"\1\2{new_quote}", body)
184         if body != new_body:
185             # Consider the string without unnecessary escapes as the original
186             body = new_body
187             s = f"{prefix}{orig_quote}{body}{orig_quote}"
188         new_body = sub_twice(escaped_orig_quote, rf"\1\2{orig_quote}", new_body)
189         new_body = sub_twice(unescaped_new_quote, rf"\1\\{new_quote}", new_body)
190     if "f" in prefix.casefold():
191         matches = re.findall(
192             r"""
193             (?:[^{]|^)\{  # start of the string or a non-{ followed by a single {
194                 ([^{].*?)  # contents of the brackets except if begins with {{
195             \}(?:[^}]|$)  # A } followed by end of the string or a non-}
196             """,
197             new_body,
198             re.VERBOSE,
199         )
200         for m in matches:
201             if "\\" in str(m):
202                 # Do not introduce backslashes in interpolated expressions
203                 return s
204 
205     if new_quote == '"""' and new_body[-1:] == '"':
206         # edge case:
207         new_body = new_body[:-1] + '\\"'
208     orig_escape_count = body.count("\\")
209     new_escape_count = new_body.count("\\")
210     if new_escape_count > orig_escape_count:
211         return s  # Do not introduce more escaping
212 
213     if new_escape_count == orig_escape_count and orig_quote == '"':
214         return s  # Prefer double quotes
215 
216     return f"{prefix}{new_quote}{new_body}{new_quote}"
217
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/src/black/strings.py b/src/black/strings.py
--- a/src/black/strings.py
+++ b/src/black/strings.py
@@ -190,9 +190,9 @@
     if "f" in prefix.casefold():
         matches = re.findall(
             r"""
-            (?:[^{]|^)\{  # start of the string or a non-{ followed by a single {
+            (?:(?<!\{)|^)\{  # start of the string or a non-{ followed by a single {
                 ([^{].*?)  # contents of the brackets except if begins with {{
-            \}(?:[^}]|$)  # A } followed by end of the string or a non-}
+            \}(?:(?!\})|$)  # A } followed by end of the string or a non-}
             """,
             new_body,
             re.VERBOSE,

{"golden_diff": "diff --git a/src/black/strings.py b/src/black/strings.py\n--- a/src/black/strings.py\n+++ b/src/black/strings.py\n@@ -190,9 +190,9 @@\n     if \"f\" in prefix.casefold():\n         matches = re.findall(\n             r\"\"\"\n-            (?:[^{]|^)\\{  # start of the string or a non-{ followed by a single {\n+            (?:(?<!\\{)|^)\\{  # start of the string or a non-{ followed by a single {\n                 ([^{].*?)  # contents of the brackets except if begins with {{\n-            \\}(?:[^}]|$)  # A } followed by end of the string or a non-}\n+            \\}(?:(?!\\})|$)  # A } followed by end of the string or a non-}\n             \"\"\",\n             new_body,\n             re.VERBOSE,\n", "issue": "Failing to parse single-quote f-string with backslash inside it\n**Describe the bug**\r\n\r\nThe formatter is failing to parse single quote f-string as it cannot include a backslash inside it.\r\n\r\n**To Reproduce**\r\n\r\n1. Take this test.py containing:\r\n```python\r\nb = \"example1\"\r\nsts = \"example2\"\r\nf\"\\\"{b}\\\"{' ' * (long-len(b)+1)}: \\\"{sts}\\\",\\n\"\r\n```\r\n2. Run black without arguments.\r\n3. See error -->\r\n```\r\n  File \"/var/task/black/__init__.py\", line 986, in assert_equivalent\r\n    dst_ast = parse_ast(dst)\r\n  File \"/var/task/black/parsing.py\", line 125, in parse_ast\r\n    raise SyntaxError(\r\nf'\"{b}\"{\\' \\' * (long-len(b)+1)}: \"{sts}\",\\n'\r\n```\r\n\r\n**Expected behavior**\r\nThe formatter is expected to throw the said error and fail to format the said file.\r\n\r\n**Environment (please complete the following information):**\r\n\r\n- Version: [main]\r\n- OS and Python version: [Linux/Python 3.9.5]\r\n\r\n**Does this bug also happen on main?**\r\nYes\r\n\n", "before_files": [{"content": "\"\"\"\nSimple formatting on strings. Further string formatting code is in trans.py.\n\"\"\"\n\nimport regex as re\nimport sys\nfrom typing import List, Pattern\n\n\nSTRING_PREFIX_CHARS = \"furbFURB\"  # All possible string prefix characters.\n\n\ndef sub_twice(regex: Pattern[str], replacement: str, original: str) -> str:\n    \"\"\"Replace `regex` with `replacement` twice on `original`.\n\n    This is used by string normalization to perform replaces on\n    overlapping matches.\n    \"\"\"\n    return regex.sub(replacement, regex.sub(replacement, original))\n\n\ndef has_triple_quotes(string: str) -> bool:\n    \"\"\"\n    Returns:\n        True iff @string starts with three quotation characters.\n    \"\"\"\n    raw_string = string.lstrip(STRING_PREFIX_CHARS)\n    return raw_string[:3] in {'\"\"\"', \"'''\"}\n\n\ndef lines_with_leading_tabs_expanded(s: str) -> List[str]:\n    \"\"\"\n    Splits string into lines and expands only leading tabs (following the normal\n    Python rules)\n    \"\"\"\n    lines = []\n    for line in s.splitlines():\n        # Find the index of the first non-whitespace character after a string of\n        # whitespace that includes at least one tab\n        match = re.match(r\"\\s*\\t+\\s*(\\S)\", line)\n        if match:\n            first_non_whitespace_idx = match.start(1)\n\n            lines.append(\n                line[:first_non_whitespace_idx].expandtabs()\n                + line[first_non_whitespace_idx:]\n            )\n        else:\n            lines.append(line)\n    return lines\n\n\ndef fix_docstring(docstring: str, prefix: str) -> str:\n    # https://www.python.org/dev/peps/pep-0257/#handling-docstring-indentation\n    if not docstring:\n        return \"\"\n    lines = lines_with_leading_tabs_expanded(docstring)\n    # Determine minimum indentation (first line doesn't count):\n    indent = sys.maxsize\n    for line in lines[1:]:\n        stripped = line.lstrip()\n        if stripped:\n            indent = min(indent, len(line) - len(stripped))\n    # Remove indentation (first line is special):\n    trimmed = [lines[0].strip()]\n    if indent < sys.maxsize:\n        last_line_idx = len(lines) - 2\n        for i, line in enumerate(lines[1:]):\n            stripped_line = line[indent:].rstrip()\n            if stripped_line or i == last_line_idx:\n                trimmed.append(prefix + stripped_line)\n            else:\n                trimmed.append(\"\")\n    return \"\\n\".join(trimmed)\n\n\ndef get_string_prefix(string: str) -> str:\n    \"\"\"\n    Pre-conditions:\n        * assert_is_leaf_string(@string)\n\n    Returns:\n        @string's prefix (e.g. '', 'r', 'f', or 'rf').\n    \"\"\"\n    assert_is_leaf_string(string)\n\n    prefix = \"\"\n    prefix_idx = 0\n    while string[prefix_idx] in STRING_PREFIX_CHARS:\n        prefix += string[prefix_idx]\n        prefix_idx += 1\n\n    return prefix\n\n\ndef assert_is_leaf_string(string: str) -> None:\n    \"\"\"\n    Checks the pre-condition that @string has the format that you would expect\n    of `leaf.value` where `leaf` is some Leaf such that `leaf.type ==\n    token.STRING`. A more precise description of the pre-conditions that are\n    checked are listed below.\n\n    Pre-conditions:\n        * @string starts with either ', \", <prefix>', or <prefix>\" where\n        `set(<prefix>)` is some subset of `set(STRING_PREFIX_CHARS)`.\n        * @string ends with a quote character (' or \").\n\n    Raises:\n        AssertionError(...) if the pre-conditions listed above are not\n        satisfied.\n    \"\"\"\n    dquote_idx = string.find('\"')\n    squote_idx = string.find(\"'\")\n    if -1 in [dquote_idx, squote_idx]:\n        quote_idx = max(dquote_idx, squote_idx)\n    else:\n        quote_idx = min(squote_idx, dquote_idx)\n\n    assert (\n        0 <= quote_idx < len(string) - 1\n    ), f\"{string!r} is missing a starting quote character (' or \\\").\"\n    assert string[-1] in (\n        \"'\",\n        '\"',\n    ), f\"{string!r} is missing an ending quote character (' or \\\").\"\n    assert set(string[:quote_idx]).issubset(\n        set(STRING_PREFIX_CHARS)\n    ), f\"{set(string[:quote_idx])} is NOT a subset of {set(STRING_PREFIX_CHARS)}.\"\n\n\ndef normalize_string_prefix(s: str, remove_u_prefix: bool = False) -> str:\n    \"\"\"Make all string prefixes lowercase.\n\n    If remove_u_prefix is given, also removes any u prefix from the string.\n    \"\"\"\n    match = re.match(r\"^([\" + STRING_PREFIX_CHARS + r\"]*)(.*)$\", s, re.DOTALL)\n    assert match is not None, f\"failed to match string {s!r}\"\n    orig_prefix = match.group(1)\n    new_prefix = orig_prefix.replace(\"F\", \"f\").replace(\"B\", \"b\").replace(\"U\", \"u\")\n    if remove_u_prefix:\n        new_prefix = new_prefix.replace(\"u\", \"\")\n    return f\"{new_prefix}{match.group(2)}\"\n\n\ndef normalize_string_quotes(s: str) -> str:\n    \"\"\"Prefer double quotes but only if it doesn't cause more escaping.\n\n    Adds or removes backslashes as appropriate. Doesn't parse and fix\n    strings nested in f-strings.\n    \"\"\"\n    value = s.lstrip(STRING_PREFIX_CHARS)\n    if value[:3] == '\"\"\"':\n        return s\n\n    elif value[:3] == \"'''\":\n        orig_quote = \"'''\"\n        new_quote = '\"\"\"'\n    elif value[0] == '\"':\n        orig_quote = '\"'\n        new_quote = \"'\"\n    else:\n        orig_quote = \"'\"\n        new_quote = '\"'\n    first_quote_pos = s.find(orig_quote)\n    if first_quote_pos == -1:\n        return s  # There's an internal error\n\n    prefix = s[:first_quote_pos]\n    unescaped_new_quote = re.compile(rf\"(([^\\\\]|^)(\\\\\\\\)*){new_quote}\")\n    escaped_new_quote = re.compile(rf\"([^\\\\]|^)\\\\((?:\\\\\\\\)*){new_quote}\")\n    escaped_orig_quote = re.compile(rf\"([^\\\\]|^)\\\\((?:\\\\\\\\)*){orig_quote}\")\n    body = s[first_quote_pos + len(orig_quote) : -len(orig_quote)]\n    if \"r\" in prefix.casefold():\n        if unescaped_new_quote.search(body):\n            # There's at least one unescaped new_quote in this raw string\n            # so converting is impossible\n            return s\n\n        # Do not introduce or remove backslashes in raw strings\n        new_body = body\n    else:\n        # remove unnecessary escapes\n        new_body = sub_twice(escaped_new_quote, rf\"\\1\\2{new_quote}\", body)\n        if body != new_body:\n            # Consider the string without unnecessary escapes as the original\n            body = new_body\n            s = f\"{prefix}{orig_quote}{body}{orig_quote}\"\n        new_body = sub_twice(escaped_orig_quote, rf\"\\1\\2{orig_quote}\", new_body)\n        new_body = sub_twice(unescaped_new_quote, rf\"\\1\\\\{new_quote}\", new_body)\n    if \"f\" in prefix.casefold():\n        matches = re.findall(\n            r\"\"\"\n            (?:[^{]|^)\\{  # start of the string or a non-{ followed by a single {\n                ([^{].*?)  # contents of the brackets except if begins with {{\n            \\}(?:[^}]|$)  # A } followed by end of the string or a non-}\n            \"\"\",\n            new_body,\n            re.VERBOSE,\n        )\n        for m in matches:\n            if \"\\\\\" in str(m):\n                # Do not introduce backslashes in interpolated expressions\n                return s\n\n    if new_quote == '\"\"\"' and new_body[-1:] == '\"':\n        # edge case:\n        new_body = new_body[:-1] + '\\\\\"'\n    orig_escape_count = body.count(\"\\\\\")\n    new_escape_count = new_body.count(\"\\\\\")\n    if new_escape_count > orig_escape_count:\n        return s  # Do not introduce more escaping\n\n    if new_escape_count == orig_escape_count and orig_quote == '\"':\n        return s  # Prefer double quotes\n\n    return f\"{prefix}{new_quote}{new_body}{new_quote}\"\n", "path": "src/black/strings.py"}], "after_files": [{"content": "\"\"\"\nSimple formatting on strings. Further string formatting code is in trans.py.\n\"\"\"\n\nimport regex as re\nimport sys\nfrom typing import List, Pattern\n\n\nSTRING_PREFIX_CHARS = \"furbFURB\"  # All possible string prefix characters.\n\n\ndef sub_twice(regex: Pattern[str], replacement: str, original: str) -> str:\n    \"\"\"Replace `regex` with `replacement` twice on `original`.\n\n    This is used by string normalization to perform replaces on\n    overlapping matches.\n    \"\"\"\n    return regex.sub(replacement, regex.sub(replacement, original))\n\n\ndef has_triple_quotes(string: str) -> bool:\n    \"\"\"\n    Returns:\n        True iff @string starts with three quotation characters.\n    \"\"\"\n    raw_string = string.lstrip(STRING_PREFIX_CHARS)\n    return raw_string[:3] in {'\"\"\"', \"'''\"}\n\n\ndef lines_with_leading_tabs_expanded(s: str) -> List[str]:\n    \"\"\"\n    Splits string into lines and expands only leading tabs (following the normal\n    Python rules)\n    \"\"\"\n    lines = []\n    for line in s.splitlines():\n        # Find the index of the first non-whitespace character after a string of\n        # whitespace that includes at least one tab\n        match = re.match(r\"\\s*\\t+\\s*(\\S)\", line)\n        if match:\n            first_non_whitespace_idx = match.start(1)\n\n            lines.append(\n                line[:first_non_whitespace_idx].expandtabs()\n                + line[first_non_whitespace_idx:]\n            )\n        else:\n            lines.append(line)\n    return lines\n\n\ndef fix_docstring(docstring: str, prefix: str) -> str:\n    # https://www.python.org/dev/peps/pep-0257/#handling-docstring-indentation\n    if not docstring:\n        return \"\"\n    lines = lines_with_leading_tabs_expanded(docstring)\n    # Determine minimum indentation (first line doesn't count):\n    indent = sys.maxsize\n    for line in lines[1:]:\n        stripped = line.lstrip()\n        if stripped:\n            indent = min(indent, len(line) - len(stripped))\n    # Remove indentation (first line is special):\n    trimmed = [lines[0].strip()]\n    if indent < sys.maxsize:\n        last_line_idx = len(lines) - 2\n        for i, line in enumerate(lines[1:]):\n            stripped_line = line[indent:].rstrip()\n            if stripped_line or i == last_line_idx:\n                trimmed.append(prefix + stripped_line)\n            else:\n                trimmed.append(\"\")\n    return \"\\n\".join(trimmed)\n\n\ndef get_string_prefix(string: str) -> str:\n    \"\"\"\n    Pre-conditions:\n        * assert_is_leaf_string(@string)\n\n    Returns:\n        @string's prefix (e.g. '', 'r', 'f', or 'rf').\n    \"\"\"\n    assert_is_leaf_string(string)\n\n    prefix = \"\"\n    prefix_idx = 0\n    while string[prefix_idx] in STRING_PREFIX_CHARS:\n        prefix += string[prefix_idx]\n        prefix_idx += 1\n\n    return prefix\n\n\ndef assert_is_leaf_string(string: str) -> None:\n    \"\"\"\n    Checks the pre-condition that @string has the format that you would expect\n    of `leaf.value` where `leaf` is some Leaf such that `leaf.type ==\n    token.STRING`. A more precise description of the pre-conditions that are\n    checked are listed below.\n\n    Pre-conditions:\n        * @string starts with either ', \", <prefix>', or <prefix>\" where\n        `set(<prefix>)` is some subset of `set(STRING_PREFIX_CHARS)`.\n        * @string ends with a quote character (' or \").\n\n    Raises:\n        AssertionError(...) if the pre-conditions listed above are not\n        satisfied.\n    \"\"\"\n    dquote_idx = string.find('\"')\n    squote_idx = string.find(\"'\")\n    if -1 in [dquote_idx, squote_idx]:\n        quote_idx = max(dquote_idx, squote_idx)\n    else:\n        quote_idx = min(squote_idx, dquote_idx)\n\n    assert (\n        0 <= quote_idx < len(string) - 1\n    ), f\"{string!r} is missing a starting quote character (' or \\\").\"\n    assert string[-1] in (\n        \"'\",\n        '\"',\n    ), f\"{string!r} is missing an ending quote character (' or \\\").\"\n    assert set(string[:quote_idx]).issubset(\n        set(STRING_PREFIX_CHARS)\n    ), f\"{set(string[:quote_idx])} is NOT a subset of {set(STRING_PREFIX_CHARS)}.\"\n\n\ndef normalize_string_prefix(s: str, remove_u_prefix: bool = False) -> str:\n    \"\"\"Make all string prefixes lowercase.\n\n    If remove_u_prefix is given, also removes any u prefix from the string.\n    \"\"\"\n    match = re.match(r\"^([\" + STRING_PREFIX_CHARS + r\"]*)(.*)$\", s, re.DOTALL)\n    assert match is not None, f\"failed to match string {s!r}\"\n    orig_prefix = match.group(1)\n    new_prefix = orig_prefix.replace(\"F\", \"f\").replace(\"B\", \"b\").replace(\"U\", \"u\")\n    if remove_u_prefix:\n        new_prefix = new_prefix.replace(\"u\", \"\")\n    return f\"{new_prefix}{match.group(2)}\"\n\n\ndef normalize_string_quotes(s: str) -> str:\n    \"\"\"Prefer double quotes but only if it doesn't cause more escaping.\n\n    Adds or removes backslashes as appropriate. Doesn't parse and fix\n    strings nested in f-strings.\n    \"\"\"\n    value = s.lstrip(STRING_PREFIX_CHARS)\n    if value[:3] == '\"\"\"':\n        return s\n\n    elif value[:3] == \"'''\":\n        orig_quote = \"'''\"\n        new_quote = '\"\"\"'\n    elif value[0] == '\"':\n        orig_quote = '\"'\n        new_quote = \"'\"\n    else:\n        orig_quote = \"'\"\n        new_quote = '\"'\n    first_quote_pos = s.find(orig_quote)\n    if first_quote_pos == -1:\n        return s  # There's an internal error\n\n    prefix = s[:first_quote_pos]\n    unescaped_new_quote = re.compile(rf\"(([^\\\\]|^)(\\\\\\\\)*){new_quote}\")\n    escaped_new_quote = re.compile(rf\"([^\\\\]|^)\\\\((?:\\\\\\\\)*){new_quote}\")\n    escaped_orig_quote = re.compile(rf\"([^\\\\]|^)\\\\((?:\\\\\\\\)*){orig_quote}\")\n    body = s[first_quote_pos + len(orig_quote) : -len(orig_quote)]\n    if \"r\" in prefix.casefold():\n        if unescaped_new_quote.search(body):\n            # There's at least one unescaped new_quote in this raw string\n            # so converting is impossible\n            return s\n\n        # Do not introduce or remove backslashes in raw strings\n        new_body = body\n    else:\n        # remove unnecessary escapes\n        new_body = sub_twice(escaped_new_quote, rf\"\\1\\2{new_quote}\", body)\n        if body != new_body:\n            # Consider the string without unnecessary escapes as the original\n            body = new_body\n            s = f\"{prefix}{orig_quote}{body}{orig_quote}\"\n        new_body = sub_twice(escaped_orig_quote, rf\"\\1\\2{orig_quote}\", new_body)\n        new_body = sub_twice(unescaped_new_quote, rf\"\\1\\\\{new_quote}\", new_body)\n    if \"f\" in prefix.casefold():\n        matches = re.findall(\n            r\"\"\"\n            (?:(?<!\\{)|^)\\{  # start of the string or a non-{ followed by a single {\n                ([^{].*?)  # contents of the brackets except if begins with {{\n            \\}(?:(?!\\})|$)  # A } followed by end of the string or a non-}\n            \"\"\",\n            new_body,\n            re.VERBOSE,\n        )\n        for m in matches:\n            if \"\\\\\" in str(m):\n                # Do not introduce backslashes in interpolated expressions\n                return s\n\n    if new_quote == '\"\"\"' and new_body[-1:] == '\"':\n        # edge case:\n        new_body = new_body[:-1] + '\\\\\"'\n    orig_escape_count = body.count(\"\\\\\")\n    new_escape_count = new_body.count(\"\\\\\")\n    if new_escape_count > orig_escape_count:\n        return s  # Do not introduce more escaping\n\n    if new_escape_count == orig_escape_count and orig_quote == '\"':\n        return s  # Prefer double quotes\n\n    return f\"{prefix}{new_quote}{new_body}{new_quote}\"\n", "path": "src/black/strings.py"}]}