We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
decide on `${ var }` vs `${{ var }}`
We have to decide on single braces vs double braces syntax. Both of them work right now, but the former is most tested internally.

1. Single braces
- For: Easier on eyes
- Against: Might affect existing users using environment variables

2. Double braces
- For: compatible with 1.0 users that were using environment variables
- Against: looks ugly

cc @dmpetrov @shcheklein 

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `dvc/parsing/interpolate.py`
Content:
```
1 import re
2 import typing
3 from collections.abc import Mapping
4 from functools import singledispatch
5 
6 from funcy import memoize, rpartial
7 
8 from dvc.exceptions import DvcException
9 
10 if typing.TYPE_CHECKING:
11     from typing import List, Match
12 
13     from pyparsing import ParseException
14     from typing_extensions import NoReturn
15 
16     from .context import Context
17 
18 BRACE_OPEN = "${"
19 BRACE_CLOSE = "}"
20 LBRACK = "["
21 RBRACK = "]"
22 PERIOD = "."
23 KEYCRE = re.compile(
24     r"""
25     (?<!\\)                   # escape \${} or ${{}}
26     \$                        # starts with $
27     (?:({{)|({))              # either starts with double braces or single
28     (.*?)                     # match every char inside
29     (?(1)}})(?(2)})           # end with same kinds of braces it opened with
30 """,
31     re.VERBOSE,
32 )
33 
34 
35 @memoize
36 def get_parser():
37     from pyparsing import CharsNotIn, ParserElement, Suppress, ZeroOrMore
38 
39     ParserElement.enablePackrat()
40 
41     word = CharsNotIn(f"{PERIOD}{LBRACK}{RBRACK}")
42     idx = Suppress(LBRACK) + word + Suppress(RBRACK)
43     attr = Suppress(PERIOD) + word
44     parser = word + ZeroOrMore(attr ^ idx)
45     parser.setParseAction(PERIOD.join)
46 
47     return parser
48 
49 
50 class ParseError(DvcException):
51     pass
52 
53 
54 def get_matches(template: str):
55     return list(KEYCRE.finditer(template))
56 
57 
58 def is_interpolated_string(val):
59     return isinstance(val, str) and bool(get_matches(val))
60 
61 
62 def normalize_key(key: str):
63     return key.replace(LBRACK, PERIOD).replace(RBRACK, "")
64 
65 
66 def format_and_raise_parse_error(exc) -> "NoReturn":
67     raise ParseError(_format_exc_msg(exc))
68 
69 
70 def embrace(s: str):
71     return BRACE_OPEN + s + BRACE_CLOSE
72 
73 
74 @singledispatch
75 def to_str(obj) -> str:
76     return str(obj)
77 
78 
79 @to_str.register(bool)
80 def _(obj: bool):
81     return "true" if obj else "false"
82 
83 
84 def _format_exc_msg(exc: "ParseException"):
85     from pyparsing import ParseException
86 
87     from dvc.utils import colorize
88 
89     exc.loc += 2  # 2 because we append `${` at the start of expr below
90 
91     expr = exc.pstr
92     exc.pstr = embrace(exc.pstr)
93     error = ParseException.explain(exc, depth=0)
94 
95     _, pointer, *explains = error.splitlines()
96     pstr = "{brace_open}{expr}{brace_close}".format(
97         brace_open=colorize(BRACE_OPEN, color="blue"),
98         expr=colorize(expr, color="magenta"),
99         brace_close=colorize(BRACE_CLOSE, color="blue"),
100     )
101     msg = "\n".join(explains)
102     pointer = colorize(pointer, color="red")
103     return "\n".join([pstr, pointer, colorize(msg, color="red", style="bold")])
104 
105 
106 def recurse(f):
107     seq = (list, tuple, set)
108 
109     def wrapper(data, *args):
110         g = rpartial(wrapper, *args)
111         if isinstance(data, Mapping):
112             return {g(k): g(v) for k, v in data.items()}
113         if isinstance(data, seq):
114             return type(data)(map(g, data))
115         if isinstance(data, str):
116             return f(data, *args)
117         return data
118 
119     return wrapper
120 
121 
122 def check_recursive_parse_errors(data):
123     func = recurse(check_expression)
124     return func(data)
125 
126 
127 def check_expression(s: str):
128     matches = get_matches(s)
129     for match in matches:
130         get_expression(match)
131 
132 
133 def parse_expr(s: str):
134     from pyparsing import ParseException
135 
136     try:
137         result = get_parser().parseString(s, parseAll=True)
138     except ParseException as exc:
139         format_and_raise_parse_error(exc)
140 
141     joined = result.asList()
142     assert len(joined) == 1
143     return joined[0]
144 
145 
146 def get_expression(match: "Match", skip_checks: bool = False):
147     _, _, inner = match.groups()
148     return inner if skip_checks else parse_expr(inner)
149 
150 
151 def str_interpolate(
152     template: str,
153     matches: "List[Match]",
154     context: "Context",
155     skip_checks: bool = False,
156 ):
157     from .context import PRIMITIVES
158 
159     index, buf = 0, ""
160     for match in matches:
161         start, end = match.span(0)
162         expr = get_expression(match, skip_checks=skip_checks)
163         value = context.select(expr, unwrap=True)
164         if value is not None and not isinstance(value, PRIMITIVES):
165             raise ParseError(
166                 f"Cannot interpolate data of type '{type(value).__name__}'"
167             )
168         buf += template[index:start] + to_str(value)
169         index = end
170     buf += template[index:]
171     # regex already backtracks and avoids any `${` starting with
172     # backslashes(`\`). We just need to replace those by `${`.
173     return buf.replace(r"\${", BRACE_OPEN)
174 
175 
176 def is_exact_string(src: str, matches: "List[Match]"):
177     return len(matches) == 1 and src == matches[0].group(0)
178
```
--- 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/dvc/parsing/interpolate.py b/dvc/parsing/interpolate.py
--- a/dvc/parsing/interpolate.py
+++ b/dvc/parsing/interpolate.py
@@ -22,11 +22,10 @@
 PERIOD = "."
 KEYCRE = re.compile(
     r"""
-    (?<!\\)                   # escape \${} or ${{}}
-    \$                        # starts with $
-    (?:({{)|({))              # either starts with double braces or single
-    (.*?)                     # match every char inside
-    (?(1)}})(?(2)})           # end with same kinds of braces it opened with
+    (?<!\\)                            # escape \${}
+    \${                                # starts with ${
+    (?P<inner>.*?)                     # match every char inside
+    }                                  # end with {
 """,
     re.VERBOSE,
 )
@@ -137,6 +136,7 @@
         result = get_parser().parseString(s, parseAll=True)
     except ParseException as exc:
         format_and_raise_parse_error(exc)
+        raise AssertionError("unreachable")
 
     joined = result.asList()
     assert len(joined) == 1
@@ -144,7 +144,7 @@
 
 
 def get_expression(match: "Match", skip_checks: bool = False):
-    _, _, inner = match.groups()
+    inner = match["inner"]
     return inner if skip_checks else parse_expr(inner)

{"golden_diff": "diff --git a/dvc/parsing/interpolate.py b/dvc/parsing/interpolate.py\n--- a/dvc/parsing/interpolate.py\n+++ b/dvc/parsing/interpolate.py\n@@ -22,11 +22,10 @@\n PERIOD = \".\"\n KEYCRE = re.compile(\n     r\"\"\"\n-    (?<!\\\\)                   # escape \\${} or ${{}}\n-    \\$                        # starts with $\n-    (?:({{)|({))              # either starts with double braces or single\n-    (.*?)                     # match every char inside\n-    (?(1)}})(?(2)})           # end with same kinds of braces it opened with\n+    (?<!\\\\)                            # escape \\${}\n+    \\${                                # starts with ${\n+    (?P<inner>.*?)                     # match every char inside\n+    }                                  # end with {\n \"\"\",\n     re.VERBOSE,\n )\n@@ -137,6 +136,7 @@\n         result = get_parser().parseString(s, parseAll=True)\n     except ParseException as exc:\n         format_and_raise_parse_error(exc)\n+        raise AssertionError(\"unreachable\")\n \n     joined = result.asList()\n     assert len(joined) == 1\n@@ -144,7 +144,7 @@\n \n \n def get_expression(match: \"Match\", skip_checks: bool = False):\n-    _, _, inner = match.groups()\n+    inner = match[\"inner\"]\n     return inner if skip_checks else parse_expr(inner)\n", "issue": "decide on `${ var }` vs `${{ var }}`\nWe have to decide on single braces vs double braces syntax. Both of them work right now, but the former is most tested internally.\r\n\r\n1. Single braces\r\n- For: Easier on eyes\r\n- Against: Might affect existing users using environment variables\r\n\r\n2. Double braces\r\n- For: compatible with 1.0 users that were using environment variables\r\n- Against: looks ugly\r\n\r\ncc @dmpetrov @shcheklein \n", "before_files": [{"content": "import re\nimport typing\nfrom collections.abc import Mapping\nfrom functools import singledispatch\n\nfrom funcy import memoize, rpartial\n\nfrom dvc.exceptions import DvcException\n\nif typing.TYPE_CHECKING:\n    from typing import List, Match\n\n    from pyparsing import ParseException\n    from typing_extensions import NoReturn\n\n    from .context import Context\n\nBRACE_OPEN = \"${\"\nBRACE_CLOSE = \"}\"\nLBRACK = \"[\"\nRBRACK = \"]\"\nPERIOD = \".\"\nKEYCRE = re.compile(\n    r\"\"\"\n    (?<!\\\\)                   # escape \\${} or ${{}}\n    \\$                        # starts with $\n    (?:({{)|({))              # either starts with double braces or single\n    (.*?)                     # match every char inside\n    (?(1)}})(?(2)})           # end with same kinds of braces it opened with\n\"\"\",\n    re.VERBOSE,\n)\n\n\n@memoize\ndef get_parser():\n    from pyparsing import CharsNotIn, ParserElement, Suppress, ZeroOrMore\n\n    ParserElement.enablePackrat()\n\n    word = CharsNotIn(f\"{PERIOD}{LBRACK}{RBRACK}\")\n    idx = Suppress(LBRACK) + word + Suppress(RBRACK)\n    attr = Suppress(PERIOD) + word\n    parser = word + ZeroOrMore(attr ^ idx)\n    parser.setParseAction(PERIOD.join)\n\n    return parser\n\n\nclass ParseError(DvcException):\n    pass\n\n\ndef get_matches(template: str):\n    return list(KEYCRE.finditer(template))\n\n\ndef is_interpolated_string(val):\n    return isinstance(val, str) and bool(get_matches(val))\n\n\ndef normalize_key(key: str):\n    return key.replace(LBRACK, PERIOD).replace(RBRACK, \"\")\n\n\ndef format_and_raise_parse_error(exc) -> \"NoReturn\":\n    raise ParseError(_format_exc_msg(exc))\n\n\ndef embrace(s: str):\n    return BRACE_OPEN + s + BRACE_CLOSE\n\n\n@singledispatch\ndef to_str(obj) -> str:\n    return str(obj)\n\n\n@to_str.register(bool)\ndef _(obj: bool):\n    return \"true\" if obj else \"false\"\n\n\ndef _format_exc_msg(exc: \"ParseException\"):\n    from pyparsing import ParseException\n\n    from dvc.utils import colorize\n\n    exc.loc += 2  # 2 because we append `${` at the start of expr below\n\n    expr = exc.pstr\n    exc.pstr = embrace(exc.pstr)\n    error = ParseException.explain(exc, depth=0)\n\n    _, pointer, *explains = error.splitlines()\n    pstr = \"{brace_open}{expr}{brace_close}\".format(\n        brace_open=colorize(BRACE_OPEN, color=\"blue\"),\n        expr=colorize(expr, color=\"magenta\"),\n        brace_close=colorize(BRACE_CLOSE, color=\"blue\"),\n    )\n    msg = \"\\n\".join(explains)\n    pointer = colorize(pointer, color=\"red\")\n    return \"\\n\".join([pstr, pointer, colorize(msg, color=\"red\", style=\"bold\")])\n\n\ndef recurse(f):\n    seq = (list, tuple, set)\n\n    def wrapper(data, *args):\n        g = rpartial(wrapper, *args)\n        if isinstance(data, Mapping):\n            return {g(k): g(v) for k, v in data.items()}\n        if isinstance(data, seq):\n            return type(data)(map(g, data))\n        if isinstance(data, str):\n            return f(data, *args)\n        return data\n\n    return wrapper\n\n\ndef check_recursive_parse_errors(data):\n    func = recurse(check_expression)\n    return func(data)\n\n\ndef check_expression(s: str):\n    matches = get_matches(s)\n    for match in matches:\n        get_expression(match)\n\n\ndef parse_expr(s: str):\n    from pyparsing import ParseException\n\n    try:\n        result = get_parser().parseString(s, parseAll=True)\n    except ParseException as exc:\n        format_and_raise_parse_error(exc)\n\n    joined = result.asList()\n    assert len(joined) == 1\n    return joined[0]\n\n\ndef get_expression(match: \"Match\", skip_checks: bool = False):\n    _, _, inner = match.groups()\n    return inner if skip_checks else parse_expr(inner)\n\n\ndef str_interpolate(\n    template: str,\n    matches: \"List[Match]\",\n    context: \"Context\",\n    skip_checks: bool = False,\n):\n    from .context import PRIMITIVES\n\n    index, buf = 0, \"\"\n    for match in matches:\n        start, end = match.span(0)\n        expr = get_expression(match, skip_checks=skip_checks)\n        value = context.select(expr, unwrap=True)\n        if value is not None and not isinstance(value, PRIMITIVES):\n            raise ParseError(\n                f\"Cannot interpolate data of type '{type(value).__name__}'\"\n            )\n        buf += template[index:start] + to_str(value)\n        index = end\n    buf += template[index:]\n    # regex already backtracks and avoids any `${` starting with\n    # backslashes(`\\`). We just need to replace those by `${`.\n    return buf.replace(r\"\\${\", BRACE_OPEN)\n\n\ndef is_exact_string(src: str, matches: \"List[Match]\"):\n    return len(matches) == 1 and src == matches[0].group(0)\n", "path": "dvc/parsing/interpolate.py"}], "after_files": [{"content": "import re\nimport typing\nfrom collections.abc import Mapping\nfrom functools import singledispatch\n\nfrom funcy import memoize, rpartial\n\nfrom dvc.exceptions import DvcException\n\nif typing.TYPE_CHECKING:\n    from typing import List, Match\n\n    from pyparsing import ParseException\n    from typing_extensions import NoReturn\n\n    from .context import Context\n\nBRACE_OPEN = \"${\"\nBRACE_CLOSE = \"}\"\nLBRACK = \"[\"\nRBRACK = \"]\"\nPERIOD = \".\"\nKEYCRE = re.compile(\n    r\"\"\"\n    (?<!\\\\)                            # escape \\${}\n    \\${                                # starts with ${\n    (?P<inner>.*?)                     # match every char inside\n    }                                  # end with {\n\"\"\",\n    re.VERBOSE,\n)\n\n\n@memoize\ndef get_parser():\n    from pyparsing import CharsNotIn, ParserElement, Suppress, ZeroOrMore\n\n    ParserElement.enablePackrat()\n\n    word = CharsNotIn(f\"{PERIOD}{LBRACK}{RBRACK}\")\n    idx = Suppress(LBRACK) + word + Suppress(RBRACK)\n    attr = Suppress(PERIOD) + word\n    parser = word + ZeroOrMore(attr ^ idx)\n    parser.setParseAction(PERIOD.join)\n\n    return parser\n\n\nclass ParseError(DvcException):\n    pass\n\n\ndef get_matches(template: str):\n    return list(KEYCRE.finditer(template))\n\n\ndef is_interpolated_string(val):\n    return isinstance(val, str) and bool(get_matches(val))\n\n\ndef normalize_key(key: str):\n    return key.replace(LBRACK, PERIOD).replace(RBRACK, \"\")\n\n\ndef format_and_raise_parse_error(exc) -> \"NoReturn\":\n    raise ParseError(_format_exc_msg(exc))\n\n\ndef embrace(s: str):\n    return BRACE_OPEN + s + BRACE_CLOSE\n\n\n@singledispatch\ndef to_str(obj) -> str:\n    return str(obj)\n\n\n@to_str.register(bool)\ndef _(obj: bool):\n    return \"true\" if obj else \"false\"\n\n\ndef _format_exc_msg(exc: \"ParseException\"):\n    from pyparsing import ParseException\n\n    from dvc.utils import colorize\n\n    exc.loc += 2  # 2 because we append `${` at the start of expr below\n\n    expr = exc.pstr\n    exc.pstr = embrace(exc.pstr)\n    error = ParseException.explain(exc, depth=0)\n\n    _, pointer, *explains = error.splitlines()\n    pstr = \"{brace_open}{expr}{brace_close}\".format(\n        brace_open=colorize(BRACE_OPEN, color=\"blue\"),\n        expr=colorize(expr, color=\"magenta\"),\n        brace_close=colorize(BRACE_CLOSE, color=\"blue\"),\n    )\n    msg = \"\\n\".join(explains)\n    pointer = colorize(pointer, color=\"red\")\n    return \"\\n\".join([pstr, pointer, colorize(msg, color=\"red\", style=\"bold\")])\n\n\ndef recurse(f):\n    seq = (list, tuple, set)\n\n    def wrapper(data, *args):\n        g = rpartial(wrapper, *args)\n        if isinstance(data, Mapping):\n            return {g(k): g(v) for k, v in data.items()}\n        if isinstance(data, seq):\n            return type(data)(map(g, data))\n        if isinstance(data, str):\n            return f(data, *args)\n        return data\n\n    return wrapper\n\n\ndef check_recursive_parse_errors(data):\n    func = recurse(check_expression)\n    return func(data)\n\n\ndef check_expression(s: str):\n    matches = get_matches(s)\n    for match in matches:\n        get_expression(match)\n\n\ndef parse_expr(s: str):\n    from pyparsing import ParseException\n\n    try:\n        result = get_parser().parseString(s, parseAll=True)\n    except ParseException as exc:\n        format_and_raise_parse_error(exc)\n        raise AssertionError(\"unreachable\")\n\n    joined = result.asList()\n    assert len(joined) == 1\n    return joined[0]\n\n\ndef get_expression(match: \"Match\", skip_checks: bool = False):\n    inner = match[\"inner\"]\n    return inner if skip_checks else parse_expr(inner)\n\n\ndef str_interpolate(\n    template: str,\n    matches: \"List[Match]\",\n    context: \"Context\",\n    skip_checks: bool = False,\n):\n    from .context import PRIMITIVES\n\n    index, buf = 0, \"\"\n    for match in matches:\n        start, end = match.span(0)\n        expr = get_expression(match, skip_checks=skip_checks)\n        value = context.select(expr, unwrap=True)\n        if value is not None and not isinstance(value, PRIMITIVES):\n            raise ParseError(\n                f\"Cannot interpolate data of type '{type(value).__name__}'\"\n            )\n        buf += template[index:start] + to_str(value)\n        index = end\n    buf += template[index:]\n    # regex already backtracks and avoids any `${` starting with\n    # backslashes(`\\`). We just need to replace those by `${`.\n    return buf.replace(r\"\\${\", BRACE_OPEN)\n\n\ndef is_exact_string(src: str, matches: \"List[Match]\"):\n    return len(matches) == 1 and src == matches[0].group(0)\n", "path": "dvc/parsing/interpolate.py"}]}