1e31aef6aSopenharmony_ci"""Parse tokens from the lexer into nodes for the compiler."""
2e31aef6aSopenharmony_ciimport typing
3e31aef6aSopenharmony_ciimport typing as t
4e31aef6aSopenharmony_ci
5e31aef6aSopenharmony_cifrom . import nodes
6e31aef6aSopenharmony_cifrom .exceptions import TemplateAssertionError
7e31aef6aSopenharmony_cifrom .exceptions import TemplateSyntaxError
8e31aef6aSopenharmony_cifrom .lexer import describe_token
9e31aef6aSopenharmony_cifrom .lexer import describe_token_expr
10e31aef6aSopenharmony_ci
11e31aef6aSopenharmony_ciif t.TYPE_CHECKING:
12e31aef6aSopenharmony_ci    import typing_extensions as te
13e31aef6aSopenharmony_ci    from .environment import Environment
14e31aef6aSopenharmony_ci
15e31aef6aSopenharmony_ci_ImportInclude = t.TypeVar("_ImportInclude", nodes.Import, nodes.Include)
16e31aef6aSopenharmony_ci_MacroCall = t.TypeVar("_MacroCall", nodes.Macro, nodes.CallBlock)
17e31aef6aSopenharmony_ci
18e31aef6aSopenharmony_ci_statement_keywords = frozenset(
19e31aef6aSopenharmony_ci    [
20e31aef6aSopenharmony_ci        "for",
21e31aef6aSopenharmony_ci        "if",
22e31aef6aSopenharmony_ci        "block",
23e31aef6aSopenharmony_ci        "extends",
24e31aef6aSopenharmony_ci        "print",
25e31aef6aSopenharmony_ci        "macro",
26e31aef6aSopenharmony_ci        "include",
27e31aef6aSopenharmony_ci        "from",
28e31aef6aSopenharmony_ci        "import",
29e31aef6aSopenharmony_ci        "set",
30e31aef6aSopenharmony_ci        "with",
31e31aef6aSopenharmony_ci        "autoescape",
32e31aef6aSopenharmony_ci    ]
33e31aef6aSopenharmony_ci)
34e31aef6aSopenharmony_ci_compare_operators = frozenset(["eq", "ne", "lt", "lteq", "gt", "gteq"])
35e31aef6aSopenharmony_ci
36e31aef6aSopenharmony_ci_math_nodes: t.Dict[str, t.Type[nodes.Expr]] = {
37e31aef6aSopenharmony_ci    "add": nodes.Add,
38e31aef6aSopenharmony_ci    "sub": nodes.Sub,
39e31aef6aSopenharmony_ci    "mul": nodes.Mul,
40e31aef6aSopenharmony_ci    "div": nodes.Div,
41e31aef6aSopenharmony_ci    "floordiv": nodes.FloorDiv,
42e31aef6aSopenharmony_ci    "mod": nodes.Mod,
43e31aef6aSopenharmony_ci}
44e31aef6aSopenharmony_ci
45e31aef6aSopenharmony_ci
46e31aef6aSopenharmony_ciclass Parser:
47e31aef6aSopenharmony_ci    """This is the central parsing class Jinja uses.  It's passed to
48e31aef6aSopenharmony_ci    extensions and can be used to parse expressions or statements.
49e31aef6aSopenharmony_ci    """
50e31aef6aSopenharmony_ci
51e31aef6aSopenharmony_ci    def __init__(
52e31aef6aSopenharmony_ci        self,
53e31aef6aSopenharmony_ci        environment: "Environment",
54e31aef6aSopenharmony_ci        source: str,
55e31aef6aSopenharmony_ci        name: t.Optional[str] = None,
56e31aef6aSopenharmony_ci        filename: t.Optional[str] = None,
57e31aef6aSopenharmony_ci        state: t.Optional[str] = None,
58e31aef6aSopenharmony_ci    ) -> None:
59e31aef6aSopenharmony_ci        self.environment = environment
60e31aef6aSopenharmony_ci        self.stream = environment._tokenize(source, name, filename, state)
61e31aef6aSopenharmony_ci        self.name = name
62e31aef6aSopenharmony_ci        self.filename = filename
63e31aef6aSopenharmony_ci        self.closed = False
64e31aef6aSopenharmony_ci        self.extensions: t.Dict[
65e31aef6aSopenharmony_ci            str, t.Callable[["Parser"], t.Union[nodes.Node, t.List[nodes.Node]]]
66e31aef6aSopenharmony_ci        ] = {}
67e31aef6aSopenharmony_ci        for extension in environment.iter_extensions():
68e31aef6aSopenharmony_ci            for tag in extension.tags:
69e31aef6aSopenharmony_ci                self.extensions[tag] = extension.parse
70e31aef6aSopenharmony_ci        self._last_identifier = 0
71e31aef6aSopenharmony_ci        self._tag_stack: t.List[str] = []
72e31aef6aSopenharmony_ci        self._end_token_stack: t.List[t.Tuple[str, ...]] = []
73e31aef6aSopenharmony_ci
74e31aef6aSopenharmony_ci    def fail(
75e31aef6aSopenharmony_ci        self,
76e31aef6aSopenharmony_ci        msg: str,
77e31aef6aSopenharmony_ci        lineno: t.Optional[int] = None,
78e31aef6aSopenharmony_ci        exc: t.Type[TemplateSyntaxError] = TemplateSyntaxError,
79e31aef6aSopenharmony_ci    ) -> "te.NoReturn":
80e31aef6aSopenharmony_ci        """Convenience method that raises `exc` with the message, passed
81e31aef6aSopenharmony_ci        line number or last line number as well as the current name and
82e31aef6aSopenharmony_ci        filename.
83e31aef6aSopenharmony_ci        """
84e31aef6aSopenharmony_ci        if lineno is None:
85e31aef6aSopenharmony_ci            lineno = self.stream.current.lineno
86e31aef6aSopenharmony_ci        raise exc(msg, lineno, self.name, self.filename)
87e31aef6aSopenharmony_ci
88e31aef6aSopenharmony_ci    def _fail_ut_eof(
89e31aef6aSopenharmony_ci        self,
90e31aef6aSopenharmony_ci        name: t.Optional[str],
91e31aef6aSopenharmony_ci        end_token_stack: t.List[t.Tuple[str, ...]],
92e31aef6aSopenharmony_ci        lineno: t.Optional[int],
93e31aef6aSopenharmony_ci    ) -> "te.NoReturn":
94e31aef6aSopenharmony_ci        expected: t.Set[str] = set()
95e31aef6aSopenharmony_ci        for exprs in end_token_stack:
96e31aef6aSopenharmony_ci            expected.update(map(describe_token_expr, exprs))
97e31aef6aSopenharmony_ci        if end_token_stack:
98e31aef6aSopenharmony_ci            currently_looking: t.Optional[str] = " or ".join(
99e31aef6aSopenharmony_ci                map(repr, map(describe_token_expr, end_token_stack[-1]))
100e31aef6aSopenharmony_ci            )
101e31aef6aSopenharmony_ci        else:
102e31aef6aSopenharmony_ci            currently_looking = None
103e31aef6aSopenharmony_ci
104e31aef6aSopenharmony_ci        if name is None:
105e31aef6aSopenharmony_ci            message = ["Unexpected end of template."]
106e31aef6aSopenharmony_ci        else:
107e31aef6aSopenharmony_ci            message = [f"Encountered unknown tag {name!r}."]
108e31aef6aSopenharmony_ci
109e31aef6aSopenharmony_ci        if currently_looking:
110e31aef6aSopenharmony_ci            if name is not None and name in expected:
111e31aef6aSopenharmony_ci                message.append(
112e31aef6aSopenharmony_ci                    "You probably made a nesting mistake. Jinja is expecting this tag,"
113e31aef6aSopenharmony_ci                    f" but currently looking for {currently_looking}."
114e31aef6aSopenharmony_ci                )
115e31aef6aSopenharmony_ci            else:
116e31aef6aSopenharmony_ci                message.append(
117e31aef6aSopenharmony_ci                    f"Jinja was looking for the following tags: {currently_looking}."
118e31aef6aSopenharmony_ci                )
119e31aef6aSopenharmony_ci
120e31aef6aSopenharmony_ci        if self._tag_stack:
121e31aef6aSopenharmony_ci            message.append(
122e31aef6aSopenharmony_ci                "The innermost block that needs to be closed is"
123e31aef6aSopenharmony_ci                f" {self._tag_stack[-1]!r}."
124e31aef6aSopenharmony_ci            )
125e31aef6aSopenharmony_ci
126e31aef6aSopenharmony_ci        self.fail(" ".join(message), lineno)
127e31aef6aSopenharmony_ci
128e31aef6aSopenharmony_ci    def fail_unknown_tag(
129e31aef6aSopenharmony_ci        self, name: str, lineno: t.Optional[int] = None
130e31aef6aSopenharmony_ci    ) -> "te.NoReturn":
131e31aef6aSopenharmony_ci        """Called if the parser encounters an unknown tag.  Tries to fail
132e31aef6aSopenharmony_ci        with a human readable error message that could help to identify
133e31aef6aSopenharmony_ci        the problem.
134e31aef6aSopenharmony_ci        """
135e31aef6aSopenharmony_ci        self._fail_ut_eof(name, self._end_token_stack, lineno)
136e31aef6aSopenharmony_ci
137e31aef6aSopenharmony_ci    def fail_eof(
138e31aef6aSopenharmony_ci        self,
139e31aef6aSopenharmony_ci        end_tokens: t.Optional[t.Tuple[str, ...]] = None,
140e31aef6aSopenharmony_ci        lineno: t.Optional[int] = None,
141e31aef6aSopenharmony_ci    ) -> "te.NoReturn":
142e31aef6aSopenharmony_ci        """Like fail_unknown_tag but for end of template situations."""
143e31aef6aSopenharmony_ci        stack = list(self._end_token_stack)
144e31aef6aSopenharmony_ci        if end_tokens is not None:
145e31aef6aSopenharmony_ci            stack.append(end_tokens)
146e31aef6aSopenharmony_ci        self._fail_ut_eof(None, stack, lineno)
147e31aef6aSopenharmony_ci
148e31aef6aSopenharmony_ci    def is_tuple_end(
149e31aef6aSopenharmony_ci        self, extra_end_rules: t.Optional[t.Tuple[str, ...]] = None
150e31aef6aSopenharmony_ci    ) -> bool:
151e31aef6aSopenharmony_ci        """Are we at the end of a tuple?"""
152e31aef6aSopenharmony_ci        if self.stream.current.type in ("variable_end", "block_end", "rparen"):
153e31aef6aSopenharmony_ci            return True
154e31aef6aSopenharmony_ci        elif extra_end_rules is not None:
155e31aef6aSopenharmony_ci            return self.stream.current.test_any(extra_end_rules)  # type: ignore
156e31aef6aSopenharmony_ci        return False
157e31aef6aSopenharmony_ci
158e31aef6aSopenharmony_ci    def free_identifier(self, lineno: t.Optional[int] = None) -> nodes.InternalName:
159e31aef6aSopenharmony_ci        """Return a new free identifier as :class:`~jinja2.nodes.InternalName`."""
160e31aef6aSopenharmony_ci        self._last_identifier += 1
161e31aef6aSopenharmony_ci        rv = object.__new__(nodes.InternalName)
162e31aef6aSopenharmony_ci        nodes.Node.__init__(rv, f"fi{self._last_identifier}", lineno=lineno)
163e31aef6aSopenharmony_ci        return rv
164e31aef6aSopenharmony_ci
165e31aef6aSopenharmony_ci    def parse_statement(self) -> t.Union[nodes.Node, t.List[nodes.Node]]:
166e31aef6aSopenharmony_ci        """Parse a single statement."""
167e31aef6aSopenharmony_ci        token = self.stream.current
168e31aef6aSopenharmony_ci        if token.type != "name":
169e31aef6aSopenharmony_ci            self.fail("tag name expected", token.lineno)
170e31aef6aSopenharmony_ci        self._tag_stack.append(token.value)
171e31aef6aSopenharmony_ci        pop_tag = True
172e31aef6aSopenharmony_ci        try:
173e31aef6aSopenharmony_ci            if token.value in _statement_keywords:
174e31aef6aSopenharmony_ci                f = getattr(self, f"parse_{self.stream.current.value}")
175e31aef6aSopenharmony_ci                return f()  # type: ignore
176e31aef6aSopenharmony_ci            if token.value == "call":
177e31aef6aSopenharmony_ci                return self.parse_call_block()
178e31aef6aSopenharmony_ci            if token.value == "filter":
179e31aef6aSopenharmony_ci                return self.parse_filter_block()
180e31aef6aSopenharmony_ci            ext = self.extensions.get(token.value)
181e31aef6aSopenharmony_ci            if ext is not None:
182e31aef6aSopenharmony_ci                return ext(self)
183e31aef6aSopenharmony_ci
184e31aef6aSopenharmony_ci            # did not work out, remove the token we pushed by accident
185e31aef6aSopenharmony_ci            # from the stack so that the unknown tag fail function can
186e31aef6aSopenharmony_ci            # produce a proper error message.
187e31aef6aSopenharmony_ci            self._tag_stack.pop()
188e31aef6aSopenharmony_ci            pop_tag = False
189e31aef6aSopenharmony_ci            self.fail_unknown_tag(token.value, token.lineno)
190e31aef6aSopenharmony_ci        finally:
191e31aef6aSopenharmony_ci            if pop_tag:
192e31aef6aSopenharmony_ci                self._tag_stack.pop()
193e31aef6aSopenharmony_ci
194e31aef6aSopenharmony_ci    def parse_statements(
195e31aef6aSopenharmony_ci        self, end_tokens: t.Tuple[str, ...], drop_needle: bool = False
196e31aef6aSopenharmony_ci    ) -> t.List[nodes.Node]:
197e31aef6aSopenharmony_ci        """Parse multiple statements into a list until one of the end tokens
198e31aef6aSopenharmony_ci        is reached.  This is used to parse the body of statements as it also
199e31aef6aSopenharmony_ci        parses template data if appropriate.  The parser checks first if the
200e31aef6aSopenharmony_ci        current token is a colon and skips it if there is one.  Then it checks
201e31aef6aSopenharmony_ci        for the block end and parses until if one of the `end_tokens` is
202e31aef6aSopenharmony_ci        reached.  Per default the active token in the stream at the end of
203e31aef6aSopenharmony_ci        the call is the matched end token.  If this is not wanted `drop_needle`
204e31aef6aSopenharmony_ci        can be set to `True` and the end token is removed.
205e31aef6aSopenharmony_ci        """
206e31aef6aSopenharmony_ci        # the first token may be a colon for python compatibility
207e31aef6aSopenharmony_ci        self.stream.skip_if("colon")
208e31aef6aSopenharmony_ci
209e31aef6aSopenharmony_ci        # in the future it would be possible to add whole code sections
210e31aef6aSopenharmony_ci        # by adding some sort of end of statement token and parsing those here.
211e31aef6aSopenharmony_ci        self.stream.expect("block_end")
212e31aef6aSopenharmony_ci        result = self.subparse(end_tokens)
213e31aef6aSopenharmony_ci
214e31aef6aSopenharmony_ci        # we reached the end of the template too early, the subparser
215e31aef6aSopenharmony_ci        # does not check for this, so we do that now
216e31aef6aSopenharmony_ci        if self.stream.current.type == "eof":
217e31aef6aSopenharmony_ci            self.fail_eof(end_tokens)
218e31aef6aSopenharmony_ci
219e31aef6aSopenharmony_ci        if drop_needle:
220e31aef6aSopenharmony_ci            next(self.stream)
221e31aef6aSopenharmony_ci        return result
222e31aef6aSopenharmony_ci
223e31aef6aSopenharmony_ci    def parse_set(self) -> t.Union[nodes.Assign, nodes.AssignBlock]:
224e31aef6aSopenharmony_ci        """Parse an assign statement."""
225e31aef6aSopenharmony_ci        lineno = next(self.stream).lineno
226e31aef6aSopenharmony_ci        target = self.parse_assign_target(with_namespace=True)
227e31aef6aSopenharmony_ci        if self.stream.skip_if("assign"):
228e31aef6aSopenharmony_ci            expr = self.parse_tuple()
229e31aef6aSopenharmony_ci            return nodes.Assign(target, expr, lineno=lineno)
230e31aef6aSopenharmony_ci        filter_node = self.parse_filter(None)
231e31aef6aSopenharmony_ci        body = self.parse_statements(("name:endset",), drop_needle=True)
232e31aef6aSopenharmony_ci        return nodes.AssignBlock(target, filter_node, body, lineno=lineno)
233e31aef6aSopenharmony_ci
234e31aef6aSopenharmony_ci    def parse_for(self) -> nodes.For:
235e31aef6aSopenharmony_ci        """Parse a for loop."""
236e31aef6aSopenharmony_ci        lineno = self.stream.expect("name:for").lineno
237e31aef6aSopenharmony_ci        target = self.parse_assign_target(extra_end_rules=("name:in",))
238e31aef6aSopenharmony_ci        self.stream.expect("name:in")
239e31aef6aSopenharmony_ci        iter = self.parse_tuple(
240e31aef6aSopenharmony_ci            with_condexpr=False, extra_end_rules=("name:recursive",)
241e31aef6aSopenharmony_ci        )
242e31aef6aSopenharmony_ci        test = None
243e31aef6aSopenharmony_ci        if self.stream.skip_if("name:if"):
244e31aef6aSopenharmony_ci            test = self.parse_expression()
245e31aef6aSopenharmony_ci        recursive = self.stream.skip_if("name:recursive")
246e31aef6aSopenharmony_ci        body = self.parse_statements(("name:endfor", "name:else"))
247e31aef6aSopenharmony_ci        if next(self.stream).value == "endfor":
248e31aef6aSopenharmony_ci            else_ = []
249e31aef6aSopenharmony_ci        else:
250e31aef6aSopenharmony_ci            else_ = self.parse_statements(("name:endfor",), drop_needle=True)
251e31aef6aSopenharmony_ci        return nodes.For(target, iter, body, else_, test, recursive, lineno=lineno)
252e31aef6aSopenharmony_ci
253e31aef6aSopenharmony_ci    def parse_if(self) -> nodes.If:
254e31aef6aSopenharmony_ci        """Parse an if construct."""
255e31aef6aSopenharmony_ci        node = result = nodes.If(lineno=self.stream.expect("name:if").lineno)
256e31aef6aSopenharmony_ci        while True:
257e31aef6aSopenharmony_ci            node.test = self.parse_tuple(with_condexpr=False)
258e31aef6aSopenharmony_ci            node.body = self.parse_statements(("name:elif", "name:else", "name:endif"))
259e31aef6aSopenharmony_ci            node.elif_ = []
260e31aef6aSopenharmony_ci            node.else_ = []
261e31aef6aSopenharmony_ci            token = next(self.stream)
262e31aef6aSopenharmony_ci            if token.test("name:elif"):
263e31aef6aSopenharmony_ci                node = nodes.If(lineno=self.stream.current.lineno)
264e31aef6aSopenharmony_ci                result.elif_.append(node)
265e31aef6aSopenharmony_ci                continue
266e31aef6aSopenharmony_ci            elif token.test("name:else"):
267e31aef6aSopenharmony_ci                result.else_ = self.parse_statements(("name:endif",), drop_needle=True)
268e31aef6aSopenharmony_ci            break
269e31aef6aSopenharmony_ci        return result
270e31aef6aSopenharmony_ci
271e31aef6aSopenharmony_ci    def parse_with(self) -> nodes.With:
272e31aef6aSopenharmony_ci        node = nodes.With(lineno=next(self.stream).lineno)
273e31aef6aSopenharmony_ci        targets: t.List[nodes.Expr] = []
274e31aef6aSopenharmony_ci        values: t.List[nodes.Expr] = []
275e31aef6aSopenharmony_ci        while self.stream.current.type != "block_end":
276e31aef6aSopenharmony_ci            if targets:
277e31aef6aSopenharmony_ci                self.stream.expect("comma")
278e31aef6aSopenharmony_ci            target = self.parse_assign_target()
279e31aef6aSopenharmony_ci            target.set_ctx("param")
280e31aef6aSopenharmony_ci            targets.append(target)
281e31aef6aSopenharmony_ci            self.stream.expect("assign")
282e31aef6aSopenharmony_ci            values.append(self.parse_expression())
283e31aef6aSopenharmony_ci        node.targets = targets
284e31aef6aSopenharmony_ci        node.values = values
285e31aef6aSopenharmony_ci        node.body = self.parse_statements(("name:endwith",), drop_needle=True)
286e31aef6aSopenharmony_ci        return node
287e31aef6aSopenharmony_ci
288e31aef6aSopenharmony_ci    def parse_autoescape(self) -> nodes.Scope:
289e31aef6aSopenharmony_ci        node = nodes.ScopedEvalContextModifier(lineno=next(self.stream).lineno)
290e31aef6aSopenharmony_ci        node.options = [nodes.Keyword("autoescape", self.parse_expression())]
291e31aef6aSopenharmony_ci        node.body = self.parse_statements(("name:endautoescape",), drop_needle=True)
292e31aef6aSopenharmony_ci        return nodes.Scope([node])
293e31aef6aSopenharmony_ci
294e31aef6aSopenharmony_ci    def parse_block(self) -> nodes.Block:
295e31aef6aSopenharmony_ci        node = nodes.Block(lineno=next(self.stream).lineno)
296e31aef6aSopenharmony_ci        node.name = self.stream.expect("name").value
297e31aef6aSopenharmony_ci        node.scoped = self.stream.skip_if("name:scoped")
298e31aef6aSopenharmony_ci        node.required = self.stream.skip_if("name:required")
299e31aef6aSopenharmony_ci
300e31aef6aSopenharmony_ci        # common problem people encounter when switching from django
301e31aef6aSopenharmony_ci        # to jinja.  we do not support hyphens in block names, so let's
302e31aef6aSopenharmony_ci        # raise a nicer error message in that case.
303e31aef6aSopenharmony_ci        if self.stream.current.type == "sub":
304e31aef6aSopenharmony_ci            self.fail(
305e31aef6aSopenharmony_ci                "Block names in Jinja have to be valid Python identifiers and may not"
306e31aef6aSopenharmony_ci                " contain hyphens, use an underscore instead."
307e31aef6aSopenharmony_ci            )
308e31aef6aSopenharmony_ci
309e31aef6aSopenharmony_ci        node.body = self.parse_statements(("name:endblock",), drop_needle=True)
310e31aef6aSopenharmony_ci
311e31aef6aSopenharmony_ci        # enforce that required blocks only contain whitespace or comments
312e31aef6aSopenharmony_ci        # by asserting that the body, if not empty, is just TemplateData nodes
313e31aef6aSopenharmony_ci        # with whitespace data
314e31aef6aSopenharmony_ci        if node.required:
315e31aef6aSopenharmony_ci            for body_node in node.body:
316e31aef6aSopenharmony_ci                if not isinstance(body_node, nodes.Output) or any(
317e31aef6aSopenharmony_ci                    not isinstance(output_node, nodes.TemplateData)
318e31aef6aSopenharmony_ci                    or not output_node.data.isspace()
319e31aef6aSopenharmony_ci                    for output_node in body_node.nodes
320e31aef6aSopenharmony_ci                ):
321e31aef6aSopenharmony_ci                    self.fail("Required blocks can only contain comments or whitespace")
322e31aef6aSopenharmony_ci
323e31aef6aSopenharmony_ci        self.stream.skip_if("name:" + node.name)
324e31aef6aSopenharmony_ci        return node
325e31aef6aSopenharmony_ci
326e31aef6aSopenharmony_ci    def parse_extends(self) -> nodes.Extends:
327e31aef6aSopenharmony_ci        node = nodes.Extends(lineno=next(self.stream).lineno)
328e31aef6aSopenharmony_ci        node.template = self.parse_expression()
329e31aef6aSopenharmony_ci        return node
330e31aef6aSopenharmony_ci
331e31aef6aSopenharmony_ci    def parse_import_context(
332e31aef6aSopenharmony_ci        self, node: _ImportInclude, default: bool
333e31aef6aSopenharmony_ci    ) -> _ImportInclude:
334e31aef6aSopenharmony_ci        if self.stream.current.test_any(
335e31aef6aSopenharmony_ci            "name:with", "name:without"
336e31aef6aSopenharmony_ci        ) and self.stream.look().test("name:context"):
337e31aef6aSopenharmony_ci            node.with_context = next(self.stream).value == "with"
338e31aef6aSopenharmony_ci            self.stream.skip()
339e31aef6aSopenharmony_ci        else:
340e31aef6aSopenharmony_ci            node.with_context = default
341e31aef6aSopenharmony_ci        return node
342e31aef6aSopenharmony_ci
343e31aef6aSopenharmony_ci    def parse_include(self) -> nodes.Include:
344e31aef6aSopenharmony_ci        node = nodes.Include(lineno=next(self.stream).lineno)
345e31aef6aSopenharmony_ci        node.template = self.parse_expression()
346e31aef6aSopenharmony_ci        if self.stream.current.test("name:ignore") and self.stream.look().test(
347e31aef6aSopenharmony_ci            "name:missing"
348e31aef6aSopenharmony_ci        ):
349e31aef6aSopenharmony_ci            node.ignore_missing = True
350e31aef6aSopenharmony_ci            self.stream.skip(2)
351e31aef6aSopenharmony_ci        else:
352e31aef6aSopenharmony_ci            node.ignore_missing = False
353e31aef6aSopenharmony_ci        return self.parse_import_context(node, True)
354e31aef6aSopenharmony_ci
355e31aef6aSopenharmony_ci    def parse_import(self) -> nodes.Import:
356e31aef6aSopenharmony_ci        node = nodes.Import(lineno=next(self.stream).lineno)
357e31aef6aSopenharmony_ci        node.template = self.parse_expression()
358e31aef6aSopenharmony_ci        self.stream.expect("name:as")
359e31aef6aSopenharmony_ci        node.target = self.parse_assign_target(name_only=True).name
360e31aef6aSopenharmony_ci        return self.parse_import_context(node, False)
361e31aef6aSopenharmony_ci
362e31aef6aSopenharmony_ci    def parse_from(self) -> nodes.FromImport:
363e31aef6aSopenharmony_ci        node = nodes.FromImport(lineno=next(self.stream).lineno)
364e31aef6aSopenharmony_ci        node.template = self.parse_expression()
365e31aef6aSopenharmony_ci        self.stream.expect("name:import")
366e31aef6aSopenharmony_ci        node.names = []
367e31aef6aSopenharmony_ci
368e31aef6aSopenharmony_ci        def parse_context() -> bool:
369e31aef6aSopenharmony_ci            if self.stream.current.value in {
370e31aef6aSopenharmony_ci                "with",
371e31aef6aSopenharmony_ci                "without",
372e31aef6aSopenharmony_ci            } and self.stream.look().test("name:context"):
373e31aef6aSopenharmony_ci                node.with_context = next(self.stream).value == "with"
374e31aef6aSopenharmony_ci                self.stream.skip()
375e31aef6aSopenharmony_ci                return True
376e31aef6aSopenharmony_ci            return False
377e31aef6aSopenharmony_ci
378e31aef6aSopenharmony_ci        while True:
379e31aef6aSopenharmony_ci            if node.names:
380e31aef6aSopenharmony_ci                self.stream.expect("comma")
381e31aef6aSopenharmony_ci            if self.stream.current.type == "name":
382e31aef6aSopenharmony_ci                if parse_context():
383e31aef6aSopenharmony_ci                    break
384e31aef6aSopenharmony_ci                target = self.parse_assign_target(name_only=True)
385e31aef6aSopenharmony_ci                if target.name.startswith("_"):
386e31aef6aSopenharmony_ci                    self.fail(
387e31aef6aSopenharmony_ci                        "names starting with an underline can not be imported",
388e31aef6aSopenharmony_ci                        target.lineno,
389e31aef6aSopenharmony_ci                        exc=TemplateAssertionError,
390e31aef6aSopenharmony_ci                    )
391e31aef6aSopenharmony_ci                if self.stream.skip_if("name:as"):
392e31aef6aSopenharmony_ci                    alias = self.parse_assign_target(name_only=True)
393e31aef6aSopenharmony_ci                    node.names.append((target.name, alias.name))
394e31aef6aSopenharmony_ci                else:
395e31aef6aSopenharmony_ci                    node.names.append(target.name)
396e31aef6aSopenharmony_ci                if parse_context() or self.stream.current.type != "comma":
397e31aef6aSopenharmony_ci                    break
398e31aef6aSopenharmony_ci            else:
399e31aef6aSopenharmony_ci                self.stream.expect("name")
400e31aef6aSopenharmony_ci        if not hasattr(node, "with_context"):
401e31aef6aSopenharmony_ci            node.with_context = False
402e31aef6aSopenharmony_ci        return node
403e31aef6aSopenharmony_ci
404e31aef6aSopenharmony_ci    def parse_signature(self, node: _MacroCall) -> None:
405e31aef6aSopenharmony_ci        args = node.args = []
406e31aef6aSopenharmony_ci        defaults = node.defaults = []
407e31aef6aSopenharmony_ci        self.stream.expect("lparen")
408e31aef6aSopenharmony_ci        while self.stream.current.type != "rparen":
409e31aef6aSopenharmony_ci            if args:
410e31aef6aSopenharmony_ci                self.stream.expect("comma")
411e31aef6aSopenharmony_ci            arg = self.parse_assign_target(name_only=True)
412e31aef6aSopenharmony_ci            arg.set_ctx("param")
413e31aef6aSopenharmony_ci            if self.stream.skip_if("assign"):
414e31aef6aSopenharmony_ci                defaults.append(self.parse_expression())
415e31aef6aSopenharmony_ci            elif defaults:
416e31aef6aSopenharmony_ci                self.fail("non-default argument follows default argument")
417e31aef6aSopenharmony_ci            args.append(arg)
418e31aef6aSopenharmony_ci        self.stream.expect("rparen")
419e31aef6aSopenharmony_ci
420e31aef6aSopenharmony_ci    def parse_call_block(self) -> nodes.CallBlock:
421e31aef6aSopenharmony_ci        node = nodes.CallBlock(lineno=next(self.stream).lineno)
422e31aef6aSopenharmony_ci        if self.stream.current.type == "lparen":
423e31aef6aSopenharmony_ci            self.parse_signature(node)
424e31aef6aSopenharmony_ci        else:
425e31aef6aSopenharmony_ci            node.args = []
426e31aef6aSopenharmony_ci            node.defaults = []
427e31aef6aSopenharmony_ci
428e31aef6aSopenharmony_ci        call_node = self.parse_expression()
429e31aef6aSopenharmony_ci        if not isinstance(call_node, nodes.Call):
430e31aef6aSopenharmony_ci            self.fail("expected call", node.lineno)
431e31aef6aSopenharmony_ci        node.call = call_node
432e31aef6aSopenharmony_ci        node.body = self.parse_statements(("name:endcall",), drop_needle=True)
433e31aef6aSopenharmony_ci        return node
434e31aef6aSopenharmony_ci
435e31aef6aSopenharmony_ci    def parse_filter_block(self) -> nodes.FilterBlock:
436e31aef6aSopenharmony_ci        node = nodes.FilterBlock(lineno=next(self.stream).lineno)
437e31aef6aSopenharmony_ci        node.filter = self.parse_filter(None, start_inline=True)  # type: ignore
438e31aef6aSopenharmony_ci        node.body = self.parse_statements(("name:endfilter",), drop_needle=True)
439e31aef6aSopenharmony_ci        return node
440e31aef6aSopenharmony_ci
441e31aef6aSopenharmony_ci    def parse_macro(self) -> nodes.Macro:
442e31aef6aSopenharmony_ci        node = nodes.Macro(lineno=next(self.stream).lineno)
443e31aef6aSopenharmony_ci        node.name = self.parse_assign_target(name_only=True).name
444e31aef6aSopenharmony_ci        self.parse_signature(node)
445e31aef6aSopenharmony_ci        node.body = self.parse_statements(("name:endmacro",), drop_needle=True)
446e31aef6aSopenharmony_ci        return node
447e31aef6aSopenharmony_ci
448e31aef6aSopenharmony_ci    def parse_print(self) -> nodes.Output:
449e31aef6aSopenharmony_ci        node = nodes.Output(lineno=next(self.stream).lineno)
450e31aef6aSopenharmony_ci        node.nodes = []
451e31aef6aSopenharmony_ci        while self.stream.current.type != "block_end":
452e31aef6aSopenharmony_ci            if node.nodes:
453e31aef6aSopenharmony_ci                self.stream.expect("comma")
454e31aef6aSopenharmony_ci            node.nodes.append(self.parse_expression())
455e31aef6aSopenharmony_ci        return node
456e31aef6aSopenharmony_ci
457e31aef6aSopenharmony_ci    @typing.overload
458e31aef6aSopenharmony_ci    def parse_assign_target(
459e31aef6aSopenharmony_ci        self, with_tuple: bool = ..., name_only: "te.Literal[True]" = ...
460e31aef6aSopenharmony_ci    ) -> nodes.Name:
461e31aef6aSopenharmony_ci        ...
462e31aef6aSopenharmony_ci
463e31aef6aSopenharmony_ci    @typing.overload
464e31aef6aSopenharmony_ci    def parse_assign_target(
465e31aef6aSopenharmony_ci        self,
466e31aef6aSopenharmony_ci        with_tuple: bool = True,
467e31aef6aSopenharmony_ci        name_only: bool = False,
468e31aef6aSopenharmony_ci        extra_end_rules: t.Optional[t.Tuple[str, ...]] = None,
469e31aef6aSopenharmony_ci        with_namespace: bool = False,
470e31aef6aSopenharmony_ci    ) -> t.Union[nodes.NSRef, nodes.Name, nodes.Tuple]:
471e31aef6aSopenharmony_ci        ...
472e31aef6aSopenharmony_ci
473e31aef6aSopenharmony_ci    def parse_assign_target(
474e31aef6aSopenharmony_ci        self,
475e31aef6aSopenharmony_ci        with_tuple: bool = True,
476e31aef6aSopenharmony_ci        name_only: bool = False,
477e31aef6aSopenharmony_ci        extra_end_rules: t.Optional[t.Tuple[str, ...]] = None,
478e31aef6aSopenharmony_ci        with_namespace: bool = False,
479e31aef6aSopenharmony_ci    ) -> t.Union[nodes.NSRef, nodes.Name, nodes.Tuple]:
480e31aef6aSopenharmony_ci        """Parse an assignment target.  As Jinja allows assignments to
481e31aef6aSopenharmony_ci        tuples, this function can parse all allowed assignment targets.  Per
482e31aef6aSopenharmony_ci        default assignments to tuples are parsed, that can be disable however
483e31aef6aSopenharmony_ci        by setting `with_tuple` to `False`.  If only assignments to names are
484e31aef6aSopenharmony_ci        wanted `name_only` can be set to `True`.  The `extra_end_rules`
485e31aef6aSopenharmony_ci        parameter is forwarded to the tuple parsing function.  If
486e31aef6aSopenharmony_ci        `with_namespace` is enabled, a namespace assignment may be parsed.
487e31aef6aSopenharmony_ci        """
488e31aef6aSopenharmony_ci        target: nodes.Expr
489e31aef6aSopenharmony_ci
490e31aef6aSopenharmony_ci        if with_namespace and self.stream.look().type == "dot":
491e31aef6aSopenharmony_ci            token = self.stream.expect("name")
492e31aef6aSopenharmony_ci            next(self.stream)  # dot
493e31aef6aSopenharmony_ci            attr = self.stream.expect("name")
494e31aef6aSopenharmony_ci            target = nodes.NSRef(token.value, attr.value, lineno=token.lineno)
495e31aef6aSopenharmony_ci        elif name_only:
496e31aef6aSopenharmony_ci            token = self.stream.expect("name")
497e31aef6aSopenharmony_ci            target = nodes.Name(token.value, "store", lineno=token.lineno)
498e31aef6aSopenharmony_ci        else:
499e31aef6aSopenharmony_ci            if with_tuple:
500e31aef6aSopenharmony_ci                target = self.parse_tuple(
501e31aef6aSopenharmony_ci                    simplified=True, extra_end_rules=extra_end_rules
502e31aef6aSopenharmony_ci                )
503e31aef6aSopenharmony_ci            else:
504e31aef6aSopenharmony_ci                target = self.parse_primary()
505e31aef6aSopenharmony_ci
506e31aef6aSopenharmony_ci            target.set_ctx("store")
507e31aef6aSopenharmony_ci
508e31aef6aSopenharmony_ci        if not target.can_assign():
509e31aef6aSopenharmony_ci            self.fail(
510e31aef6aSopenharmony_ci                f"can't assign to {type(target).__name__.lower()!r}", target.lineno
511e31aef6aSopenharmony_ci            )
512e31aef6aSopenharmony_ci
513e31aef6aSopenharmony_ci        return target  # type: ignore
514e31aef6aSopenharmony_ci
515e31aef6aSopenharmony_ci    def parse_expression(self, with_condexpr: bool = True) -> nodes.Expr:
516e31aef6aSopenharmony_ci        """Parse an expression.  Per default all expressions are parsed, if
517e31aef6aSopenharmony_ci        the optional `with_condexpr` parameter is set to `False` conditional
518e31aef6aSopenharmony_ci        expressions are not parsed.
519e31aef6aSopenharmony_ci        """
520e31aef6aSopenharmony_ci        if with_condexpr:
521e31aef6aSopenharmony_ci            return self.parse_condexpr()
522e31aef6aSopenharmony_ci        return self.parse_or()
523e31aef6aSopenharmony_ci
524e31aef6aSopenharmony_ci    def parse_condexpr(self) -> nodes.Expr:
525e31aef6aSopenharmony_ci        lineno = self.stream.current.lineno
526e31aef6aSopenharmony_ci        expr1 = self.parse_or()
527e31aef6aSopenharmony_ci        expr3: t.Optional[nodes.Expr]
528e31aef6aSopenharmony_ci
529e31aef6aSopenharmony_ci        while self.stream.skip_if("name:if"):
530e31aef6aSopenharmony_ci            expr2 = self.parse_or()
531e31aef6aSopenharmony_ci            if self.stream.skip_if("name:else"):
532e31aef6aSopenharmony_ci                expr3 = self.parse_condexpr()
533e31aef6aSopenharmony_ci            else:
534e31aef6aSopenharmony_ci                expr3 = None
535e31aef6aSopenharmony_ci            expr1 = nodes.CondExpr(expr2, expr1, expr3, lineno=lineno)
536e31aef6aSopenharmony_ci            lineno = self.stream.current.lineno
537e31aef6aSopenharmony_ci        return expr1
538e31aef6aSopenharmony_ci
539e31aef6aSopenharmony_ci    def parse_or(self) -> nodes.Expr:
540e31aef6aSopenharmony_ci        lineno = self.stream.current.lineno
541e31aef6aSopenharmony_ci        left = self.parse_and()
542e31aef6aSopenharmony_ci        while self.stream.skip_if("name:or"):
543e31aef6aSopenharmony_ci            right = self.parse_and()
544e31aef6aSopenharmony_ci            left = nodes.Or(left, right, lineno=lineno)
545e31aef6aSopenharmony_ci            lineno = self.stream.current.lineno
546e31aef6aSopenharmony_ci        return left
547e31aef6aSopenharmony_ci
548e31aef6aSopenharmony_ci    def parse_and(self) -> nodes.Expr:
549e31aef6aSopenharmony_ci        lineno = self.stream.current.lineno
550e31aef6aSopenharmony_ci        left = self.parse_not()
551e31aef6aSopenharmony_ci        while self.stream.skip_if("name:and"):
552e31aef6aSopenharmony_ci            right = self.parse_not()
553e31aef6aSopenharmony_ci            left = nodes.And(left, right, lineno=lineno)
554e31aef6aSopenharmony_ci            lineno = self.stream.current.lineno
555e31aef6aSopenharmony_ci        return left
556e31aef6aSopenharmony_ci
557e31aef6aSopenharmony_ci    def parse_not(self) -> nodes.Expr:
558e31aef6aSopenharmony_ci        if self.stream.current.test("name:not"):
559e31aef6aSopenharmony_ci            lineno = next(self.stream).lineno
560e31aef6aSopenharmony_ci            return nodes.Not(self.parse_not(), lineno=lineno)
561e31aef6aSopenharmony_ci        return self.parse_compare()
562e31aef6aSopenharmony_ci
563e31aef6aSopenharmony_ci    def parse_compare(self) -> nodes.Expr:
564e31aef6aSopenharmony_ci        lineno = self.stream.current.lineno
565e31aef6aSopenharmony_ci        expr = self.parse_math1()
566e31aef6aSopenharmony_ci        ops = []
567e31aef6aSopenharmony_ci        while True:
568e31aef6aSopenharmony_ci            token_type = self.stream.current.type
569e31aef6aSopenharmony_ci            if token_type in _compare_operators:
570e31aef6aSopenharmony_ci                next(self.stream)
571e31aef6aSopenharmony_ci                ops.append(nodes.Operand(token_type, self.parse_math1()))
572e31aef6aSopenharmony_ci            elif self.stream.skip_if("name:in"):
573e31aef6aSopenharmony_ci                ops.append(nodes.Operand("in", self.parse_math1()))
574e31aef6aSopenharmony_ci            elif self.stream.current.test("name:not") and self.stream.look().test(
575e31aef6aSopenharmony_ci                "name:in"
576e31aef6aSopenharmony_ci            ):
577e31aef6aSopenharmony_ci                self.stream.skip(2)
578e31aef6aSopenharmony_ci                ops.append(nodes.Operand("notin", self.parse_math1()))
579e31aef6aSopenharmony_ci            else:
580e31aef6aSopenharmony_ci                break
581e31aef6aSopenharmony_ci            lineno = self.stream.current.lineno
582e31aef6aSopenharmony_ci        if not ops:
583e31aef6aSopenharmony_ci            return expr
584e31aef6aSopenharmony_ci        return nodes.Compare(expr, ops, lineno=lineno)
585e31aef6aSopenharmony_ci
586e31aef6aSopenharmony_ci    def parse_math1(self) -> nodes.Expr:
587e31aef6aSopenharmony_ci        lineno = self.stream.current.lineno
588e31aef6aSopenharmony_ci        left = self.parse_concat()
589e31aef6aSopenharmony_ci        while self.stream.current.type in ("add", "sub"):
590e31aef6aSopenharmony_ci            cls = _math_nodes[self.stream.current.type]
591e31aef6aSopenharmony_ci            next(self.stream)
592e31aef6aSopenharmony_ci            right = self.parse_concat()
593e31aef6aSopenharmony_ci            left = cls(left, right, lineno=lineno)
594e31aef6aSopenharmony_ci            lineno = self.stream.current.lineno
595e31aef6aSopenharmony_ci        return left
596e31aef6aSopenharmony_ci
597e31aef6aSopenharmony_ci    def parse_concat(self) -> nodes.Expr:
598e31aef6aSopenharmony_ci        lineno = self.stream.current.lineno
599e31aef6aSopenharmony_ci        args = [self.parse_math2()]
600e31aef6aSopenharmony_ci        while self.stream.current.type == "tilde":
601e31aef6aSopenharmony_ci            next(self.stream)
602e31aef6aSopenharmony_ci            args.append(self.parse_math2())
603e31aef6aSopenharmony_ci        if len(args) == 1:
604e31aef6aSopenharmony_ci            return args[0]
605e31aef6aSopenharmony_ci        return nodes.Concat(args, lineno=lineno)
606e31aef6aSopenharmony_ci
607e31aef6aSopenharmony_ci    def parse_math2(self) -> nodes.Expr:
608e31aef6aSopenharmony_ci        lineno = self.stream.current.lineno
609e31aef6aSopenharmony_ci        left = self.parse_pow()
610e31aef6aSopenharmony_ci        while self.stream.current.type in ("mul", "div", "floordiv", "mod"):
611e31aef6aSopenharmony_ci            cls = _math_nodes[self.stream.current.type]
612e31aef6aSopenharmony_ci            next(self.stream)
613e31aef6aSopenharmony_ci            right = self.parse_pow()
614e31aef6aSopenharmony_ci            left = cls(left, right, lineno=lineno)
615e31aef6aSopenharmony_ci            lineno = self.stream.current.lineno
616e31aef6aSopenharmony_ci        return left
617e31aef6aSopenharmony_ci
618e31aef6aSopenharmony_ci    def parse_pow(self) -> nodes.Expr:
619e31aef6aSopenharmony_ci        lineno = self.stream.current.lineno
620e31aef6aSopenharmony_ci        left = self.parse_unary()
621e31aef6aSopenharmony_ci        while self.stream.current.type == "pow":
622e31aef6aSopenharmony_ci            next(self.stream)
623e31aef6aSopenharmony_ci            right = self.parse_unary()
624e31aef6aSopenharmony_ci            left = nodes.Pow(left, right, lineno=lineno)
625e31aef6aSopenharmony_ci            lineno = self.stream.current.lineno
626e31aef6aSopenharmony_ci        return left
627e31aef6aSopenharmony_ci
628e31aef6aSopenharmony_ci    def parse_unary(self, with_filter: bool = True) -> nodes.Expr:
629e31aef6aSopenharmony_ci        token_type = self.stream.current.type
630e31aef6aSopenharmony_ci        lineno = self.stream.current.lineno
631e31aef6aSopenharmony_ci        node: nodes.Expr
632e31aef6aSopenharmony_ci
633e31aef6aSopenharmony_ci        if token_type == "sub":
634e31aef6aSopenharmony_ci            next(self.stream)
635e31aef6aSopenharmony_ci            node = nodes.Neg(self.parse_unary(False), lineno=lineno)
636e31aef6aSopenharmony_ci        elif token_type == "add":
637e31aef6aSopenharmony_ci            next(self.stream)
638e31aef6aSopenharmony_ci            node = nodes.Pos(self.parse_unary(False), lineno=lineno)
639e31aef6aSopenharmony_ci        else:
640e31aef6aSopenharmony_ci            node = self.parse_primary()
641e31aef6aSopenharmony_ci        node = self.parse_postfix(node)
642e31aef6aSopenharmony_ci        if with_filter:
643e31aef6aSopenharmony_ci            node = self.parse_filter_expr(node)
644e31aef6aSopenharmony_ci        return node
645e31aef6aSopenharmony_ci
646e31aef6aSopenharmony_ci    def parse_primary(self) -> nodes.Expr:
647e31aef6aSopenharmony_ci        token = self.stream.current
648e31aef6aSopenharmony_ci        node: nodes.Expr
649e31aef6aSopenharmony_ci        if token.type == "name":
650e31aef6aSopenharmony_ci            if token.value in ("true", "false", "True", "False"):
651e31aef6aSopenharmony_ci                node = nodes.Const(token.value in ("true", "True"), lineno=token.lineno)
652e31aef6aSopenharmony_ci            elif token.value in ("none", "None"):
653e31aef6aSopenharmony_ci                node = nodes.Const(None, lineno=token.lineno)
654e31aef6aSopenharmony_ci            else:
655e31aef6aSopenharmony_ci                node = nodes.Name(token.value, "load", lineno=token.lineno)
656e31aef6aSopenharmony_ci            next(self.stream)
657e31aef6aSopenharmony_ci        elif token.type == "string":
658e31aef6aSopenharmony_ci            next(self.stream)
659e31aef6aSopenharmony_ci            buf = [token.value]
660e31aef6aSopenharmony_ci            lineno = token.lineno
661e31aef6aSopenharmony_ci            while self.stream.current.type == "string":
662e31aef6aSopenharmony_ci                buf.append(self.stream.current.value)
663e31aef6aSopenharmony_ci                next(self.stream)
664e31aef6aSopenharmony_ci            node = nodes.Const("".join(buf), lineno=lineno)
665e31aef6aSopenharmony_ci        elif token.type in ("integer", "float"):
666e31aef6aSopenharmony_ci            next(self.stream)
667e31aef6aSopenharmony_ci            node = nodes.Const(token.value, lineno=token.lineno)
668e31aef6aSopenharmony_ci        elif token.type == "lparen":
669e31aef6aSopenharmony_ci            next(self.stream)
670e31aef6aSopenharmony_ci            node = self.parse_tuple(explicit_parentheses=True)
671e31aef6aSopenharmony_ci            self.stream.expect("rparen")
672e31aef6aSopenharmony_ci        elif token.type == "lbracket":
673e31aef6aSopenharmony_ci            node = self.parse_list()
674e31aef6aSopenharmony_ci        elif token.type == "lbrace":
675e31aef6aSopenharmony_ci            node = self.parse_dict()
676e31aef6aSopenharmony_ci        else:
677e31aef6aSopenharmony_ci            self.fail(f"unexpected {describe_token(token)!r}", token.lineno)
678e31aef6aSopenharmony_ci        return node
679e31aef6aSopenharmony_ci
680e31aef6aSopenharmony_ci    def parse_tuple(
681e31aef6aSopenharmony_ci        self,
682e31aef6aSopenharmony_ci        simplified: bool = False,
683e31aef6aSopenharmony_ci        with_condexpr: bool = True,
684e31aef6aSopenharmony_ci        extra_end_rules: t.Optional[t.Tuple[str, ...]] = None,
685e31aef6aSopenharmony_ci        explicit_parentheses: bool = False,
686e31aef6aSopenharmony_ci    ) -> t.Union[nodes.Tuple, nodes.Expr]:
687e31aef6aSopenharmony_ci        """Works like `parse_expression` but if multiple expressions are
688e31aef6aSopenharmony_ci        delimited by a comma a :class:`~jinja2.nodes.Tuple` node is created.
689e31aef6aSopenharmony_ci        This method could also return a regular expression instead of a tuple
690e31aef6aSopenharmony_ci        if no commas where found.
691e31aef6aSopenharmony_ci
692e31aef6aSopenharmony_ci        The default parsing mode is a full tuple.  If `simplified` is `True`
693e31aef6aSopenharmony_ci        only names and literals are parsed.  The `no_condexpr` parameter is
694e31aef6aSopenharmony_ci        forwarded to :meth:`parse_expression`.
695e31aef6aSopenharmony_ci
696e31aef6aSopenharmony_ci        Because tuples do not require delimiters and may end in a bogus comma
697e31aef6aSopenharmony_ci        an extra hint is needed that marks the end of a tuple.  For example
698e31aef6aSopenharmony_ci        for loops support tuples between `for` and `in`.  In that case the
699e31aef6aSopenharmony_ci        `extra_end_rules` is set to ``['name:in']``.
700e31aef6aSopenharmony_ci
701e31aef6aSopenharmony_ci        `explicit_parentheses` is true if the parsing was triggered by an
702e31aef6aSopenharmony_ci        expression in parentheses.  This is used to figure out if an empty
703e31aef6aSopenharmony_ci        tuple is a valid expression or not.
704e31aef6aSopenharmony_ci        """
705e31aef6aSopenharmony_ci        lineno = self.stream.current.lineno
706e31aef6aSopenharmony_ci        if simplified:
707e31aef6aSopenharmony_ci            parse = self.parse_primary
708e31aef6aSopenharmony_ci        elif with_condexpr:
709e31aef6aSopenharmony_ci            parse = self.parse_expression
710e31aef6aSopenharmony_ci        else:
711e31aef6aSopenharmony_ci
712e31aef6aSopenharmony_ci            def parse() -> nodes.Expr:
713e31aef6aSopenharmony_ci                return self.parse_expression(with_condexpr=False)
714e31aef6aSopenharmony_ci
715e31aef6aSopenharmony_ci        args: t.List[nodes.Expr] = []
716e31aef6aSopenharmony_ci        is_tuple = False
717e31aef6aSopenharmony_ci
718e31aef6aSopenharmony_ci        while True:
719e31aef6aSopenharmony_ci            if args:
720e31aef6aSopenharmony_ci                self.stream.expect("comma")
721e31aef6aSopenharmony_ci            if self.is_tuple_end(extra_end_rules):
722e31aef6aSopenharmony_ci                break
723e31aef6aSopenharmony_ci            args.append(parse())
724e31aef6aSopenharmony_ci            if self.stream.current.type == "comma":
725e31aef6aSopenharmony_ci                is_tuple = True
726e31aef6aSopenharmony_ci            else:
727e31aef6aSopenharmony_ci                break
728e31aef6aSopenharmony_ci            lineno = self.stream.current.lineno
729e31aef6aSopenharmony_ci
730e31aef6aSopenharmony_ci        if not is_tuple:
731e31aef6aSopenharmony_ci            if args:
732e31aef6aSopenharmony_ci                return args[0]
733e31aef6aSopenharmony_ci
734e31aef6aSopenharmony_ci            # if we don't have explicit parentheses, an empty tuple is
735e31aef6aSopenharmony_ci            # not a valid expression.  This would mean nothing (literally
736e31aef6aSopenharmony_ci            # nothing) in the spot of an expression would be an empty
737e31aef6aSopenharmony_ci            # tuple.
738e31aef6aSopenharmony_ci            if not explicit_parentheses:
739e31aef6aSopenharmony_ci                self.fail(
740e31aef6aSopenharmony_ci                    "Expected an expression,"
741e31aef6aSopenharmony_ci                    f" got {describe_token(self.stream.current)!r}"
742e31aef6aSopenharmony_ci                )
743e31aef6aSopenharmony_ci
744e31aef6aSopenharmony_ci        return nodes.Tuple(args, "load", lineno=lineno)
745e31aef6aSopenharmony_ci
746e31aef6aSopenharmony_ci    def parse_list(self) -> nodes.List:
747e31aef6aSopenharmony_ci        token = self.stream.expect("lbracket")
748e31aef6aSopenharmony_ci        items: t.List[nodes.Expr] = []
749e31aef6aSopenharmony_ci        while self.stream.current.type != "rbracket":
750e31aef6aSopenharmony_ci            if items:
751e31aef6aSopenharmony_ci                self.stream.expect("comma")
752e31aef6aSopenharmony_ci            if self.stream.current.type == "rbracket":
753e31aef6aSopenharmony_ci                break
754e31aef6aSopenharmony_ci            items.append(self.parse_expression())
755e31aef6aSopenharmony_ci        self.stream.expect("rbracket")
756e31aef6aSopenharmony_ci        return nodes.List(items, lineno=token.lineno)
757e31aef6aSopenharmony_ci
758e31aef6aSopenharmony_ci    def parse_dict(self) -> nodes.Dict:
759e31aef6aSopenharmony_ci        token = self.stream.expect("lbrace")
760e31aef6aSopenharmony_ci        items: t.List[nodes.Pair] = []
761e31aef6aSopenharmony_ci        while self.stream.current.type != "rbrace":
762e31aef6aSopenharmony_ci            if items:
763e31aef6aSopenharmony_ci                self.stream.expect("comma")
764e31aef6aSopenharmony_ci            if self.stream.current.type == "rbrace":
765e31aef6aSopenharmony_ci                break
766e31aef6aSopenharmony_ci            key = self.parse_expression()
767e31aef6aSopenharmony_ci            self.stream.expect("colon")
768e31aef6aSopenharmony_ci            value = self.parse_expression()
769e31aef6aSopenharmony_ci            items.append(nodes.Pair(key, value, lineno=key.lineno))
770e31aef6aSopenharmony_ci        self.stream.expect("rbrace")
771e31aef6aSopenharmony_ci        return nodes.Dict(items, lineno=token.lineno)
772e31aef6aSopenharmony_ci
773e31aef6aSopenharmony_ci    def parse_postfix(self, node: nodes.Expr) -> nodes.Expr:
774e31aef6aSopenharmony_ci        while True:
775e31aef6aSopenharmony_ci            token_type = self.stream.current.type
776e31aef6aSopenharmony_ci            if token_type == "dot" or token_type == "lbracket":
777e31aef6aSopenharmony_ci                node = self.parse_subscript(node)
778e31aef6aSopenharmony_ci            # calls are valid both after postfix expressions (getattr
779e31aef6aSopenharmony_ci            # and getitem) as well as filters and tests
780e31aef6aSopenharmony_ci            elif token_type == "lparen":
781e31aef6aSopenharmony_ci                node = self.parse_call(node)
782e31aef6aSopenharmony_ci            else:
783e31aef6aSopenharmony_ci                break
784e31aef6aSopenharmony_ci        return node
785e31aef6aSopenharmony_ci
786e31aef6aSopenharmony_ci    def parse_filter_expr(self, node: nodes.Expr) -> nodes.Expr:
787e31aef6aSopenharmony_ci        while True:
788e31aef6aSopenharmony_ci            token_type = self.stream.current.type
789e31aef6aSopenharmony_ci            if token_type == "pipe":
790e31aef6aSopenharmony_ci                node = self.parse_filter(node)  # type: ignore
791e31aef6aSopenharmony_ci            elif token_type == "name" and self.stream.current.value == "is":
792e31aef6aSopenharmony_ci                node = self.parse_test(node)
793e31aef6aSopenharmony_ci            # calls are valid both after postfix expressions (getattr
794e31aef6aSopenharmony_ci            # and getitem) as well as filters and tests
795e31aef6aSopenharmony_ci            elif token_type == "lparen":
796e31aef6aSopenharmony_ci                node = self.parse_call(node)
797e31aef6aSopenharmony_ci            else:
798e31aef6aSopenharmony_ci                break
799e31aef6aSopenharmony_ci        return node
800e31aef6aSopenharmony_ci
801e31aef6aSopenharmony_ci    def parse_subscript(
802e31aef6aSopenharmony_ci        self, node: nodes.Expr
803e31aef6aSopenharmony_ci    ) -> t.Union[nodes.Getattr, nodes.Getitem]:
804e31aef6aSopenharmony_ci        token = next(self.stream)
805e31aef6aSopenharmony_ci        arg: nodes.Expr
806e31aef6aSopenharmony_ci
807e31aef6aSopenharmony_ci        if token.type == "dot":
808e31aef6aSopenharmony_ci            attr_token = self.stream.current
809e31aef6aSopenharmony_ci            next(self.stream)
810e31aef6aSopenharmony_ci            if attr_token.type == "name":
811e31aef6aSopenharmony_ci                return nodes.Getattr(
812e31aef6aSopenharmony_ci                    node, attr_token.value, "load", lineno=token.lineno
813e31aef6aSopenharmony_ci                )
814e31aef6aSopenharmony_ci            elif attr_token.type != "integer":
815e31aef6aSopenharmony_ci                self.fail("expected name or number", attr_token.lineno)
816e31aef6aSopenharmony_ci            arg = nodes.Const(attr_token.value, lineno=attr_token.lineno)
817e31aef6aSopenharmony_ci            return nodes.Getitem(node, arg, "load", lineno=token.lineno)
818e31aef6aSopenharmony_ci        if token.type == "lbracket":
819e31aef6aSopenharmony_ci            args: t.List[nodes.Expr] = []
820e31aef6aSopenharmony_ci            while self.stream.current.type != "rbracket":
821e31aef6aSopenharmony_ci                if args:
822e31aef6aSopenharmony_ci                    self.stream.expect("comma")
823e31aef6aSopenharmony_ci                args.append(self.parse_subscribed())
824e31aef6aSopenharmony_ci            self.stream.expect("rbracket")
825e31aef6aSopenharmony_ci            if len(args) == 1:
826e31aef6aSopenharmony_ci                arg = args[0]
827e31aef6aSopenharmony_ci            else:
828e31aef6aSopenharmony_ci                arg = nodes.Tuple(args, "load", lineno=token.lineno)
829e31aef6aSopenharmony_ci            return nodes.Getitem(node, arg, "load", lineno=token.lineno)
830e31aef6aSopenharmony_ci        self.fail("expected subscript expression", token.lineno)
831e31aef6aSopenharmony_ci
832e31aef6aSopenharmony_ci    def parse_subscribed(self) -> nodes.Expr:
833e31aef6aSopenharmony_ci        lineno = self.stream.current.lineno
834e31aef6aSopenharmony_ci        args: t.List[t.Optional[nodes.Expr]]
835e31aef6aSopenharmony_ci
836e31aef6aSopenharmony_ci        if self.stream.current.type == "colon":
837e31aef6aSopenharmony_ci            next(self.stream)
838e31aef6aSopenharmony_ci            args = [None]
839e31aef6aSopenharmony_ci        else:
840e31aef6aSopenharmony_ci            node = self.parse_expression()
841e31aef6aSopenharmony_ci            if self.stream.current.type != "colon":
842e31aef6aSopenharmony_ci                return node
843e31aef6aSopenharmony_ci            next(self.stream)
844e31aef6aSopenharmony_ci            args = [node]
845e31aef6aSopenharmony_ci
846e31aef6aSopenharmony_ci        if self.stream.current.type == "colon":
847e31aef6aSopenharmony_ci            args.append(None)
848e31aef6aSopenharmony_ci        elif self.stream.current.type not in ("rbracket", "comma"):
849e31aef6aSopenharmony_ci            args.append(self.parse_expression())
850e31aef6aSopenharmony_ci        else:
851e31aef6aSopenharmony_ci            args.append(None)
852e31aef6aSopenharmony_ci
853e31aef6aSopenharmony_ci        if self.stream.current.type == "colon":
854e31aef6aSopenharmony_ci            next(self.stream)
855e31aef6aSopenharmony_ci            if self.stream.current.type not in ("rbracket", "comma"):
856e31aef6aSopenharmony_ci                args.append(self.parse_expression())
857e31aef6aSopenharmony_ci            else:
858e31aef6aSopenharmony_ci                args.append(None)
859e31aef6aSopenharmony_ci        else:
860e31aef6aSopenharmony_ci            args.append(None)
861e31aef6aSopenharmony_ci
862e31aef6aSopenharmony_ci        return nodes.Slice(lineno=lineno, *args)  # noqa: B026
863e31aef6aSopenharmony_ci
864e31aef6aSopenharmony_ci    def parse_call_args(self) -> t.Tuple:
865e31aef6aSopenharmony_ci        token = self.stream.expect("lparen")
866e31aef6aSopenharmony_ci        args = []
867e31aef6aSopenharmony_ci        kwargs = []
868e31aef6aSopenharmony_ci        dyn_args = None
869e31aef6aSopenharmony_ci        dyn_kwargs = None
870e31aef6aSopenharmony_ci        require_comma = False
871e31aef6aSopenharmony_ci
872e31aef6aSopenharmony_ci        def ensure(expr: bool) -> None:
873e31aef6aSopenharmony_ci            if not expr:
874e31aef6aSopenharmony_ci                self.fail("invalid syntax for function call expression", token.lineno)
875e31aef6aSopenharmony_ci
876e31aef6aSopenharmony_ci        while self.stream.current.type != "rparen":
877e31aef6aSopenharmony_ci            if require_comma:
878e31aef6aSopenharmony_ci                self.stream.expect("comma")
879e31aef6aSopenharmony_ci
880e31aef6aSopenharmony_ci                # support for trailing comma
881e31aef6aSopenharmony_ci                if self.stream.current.type == "rparen":
882e31aef6aSopenharmony_ci                    break
883e31aef6aSopenharmony_ci
884e31aef6aSopenharmony_ci            if self.stream.current.type == "mul":
885e31aef6aSopenharmony_ci                ensure(dyn_args is None and dyn_kwargs is None)
886e31aef6aSopenharmony_ci                next(self.stream)
887e31aef6aSopenharmony_ci                dyn_args = self.parse_expression()
888e31aef6aSopenharmony_ci            elif self.stream.current.type == "pow":
889e31aef6aSopenharmony_ci                ensure(dyn_kwargs is None)
890e31aef6aSopenharmony_ci                next(self.stream)
891e31aef6aSopenharmony_ci                dyn_kwargs = self.parse_expression()
892e31aef6aSopenharmony_ci            else:
893e31aef6aSopenharmony_ci                if (
894e31aef6aSopenharmony_ci                    self.stream.current.type == "name"
895e31aef6aSopenharmony_ci                    and self.stream.look().type == "assign"
896e31aef6aSopenharmony_ci                ):
897e31aef6aSopenharmony_ci                    # Parsing a kwarg
898e31aef6aSopenharmony_ci                    ensure(dyn_kwargs is None)
899e31aef6aSopenharmony_ci                    key = self.stream.current.value
900e31aef6aSopenharmony_ci                    self.stream.skip(2)
901e31aef6aSopenharmony_ci                    value = self.parse_expression()
902e31aef6aSopenharmony_ci                    kwargs.append(nodes.Keyword(key, value, lineno=value.lineno))
903e31aef6aSopenharmony_ci                else:
904e31aef6aSopenharmony_ci                    # Parsing an arg
905e31aef6aSopenharmony_ci                    ensure(dyn_args is None and dyn_kwargs is None and not kwargs)
906e31aef6aSopenharmony_ci                    args.append(self.parse_expression())
907e31aef6aSopenharmony_ci
908e31aef6aSopenharmony_ci            require_comma = True
909e31aef6aSopenharmony_ci
910e31aef6aSopenharmony_ci        self.stream.expect("rparen")
911e31aef6aSopenharmony_ci        return args, kwargs, dyn_args, dyn_kwargs
912e31aef6aSopenharmony_ci
913e31aef6aSopenharmony_ci    def parse_call(self, node: nodes.Expr) -> nodes.Call:
914e31aef6aSopenharmony_ci        # The lparen will be expected in parse_call_args, but the lineno
915e31aef6aSopenharmony_ci        # needs to be recorded before the stream is advanced.
916e31aef6aSopenharmony_ci        token = self.stream.current
917e31aef6aSopenharmony_ci        args, kwargs, dyn_args, dyn_kwargs = self.parse_call_args()
918e31aef6aSopenharmony_ci        return nodes.Call(node, args, kwargs, dyn_args, dyn_kwargs, lineno=token.lineno)
919e31aef6aSopenharmony_ci
920e31aef6aSopenharmony_ci    def parse_filter(
921e31aef6aSopenharmony_ci        self, node: t.Optional[nodes.Expr], start_inline: bool = False
922e31aef6aSopenharmony_ci    ) -> t.Optional[nodes.Expr]:
923e31aef6aSopenharmony_ci        while self.stream.current.type == "pipe" or start_inline:
924e31aef6aSopenharmony_ci            if not start_inline:
925e31aef6aSopenharmony_ci                next(self.stream)
926e31aef6aSopenharmony_ci            token = self.stream.expect("name")
927e31aef6aSopenharmony_ci            name = token.value
928e31aef6aSopenharmony_ci            while self.stream.current.type == "dot":
929e31aef6aSopenharmony_ci                next(self.stream)
930e31aef6aSopenharmony_ci                name += "." + self.stream.expect("name").value
931e31aef6aSopenharmony_ci            if self.stream.current.type == "lparen":
932e31aef6aSopenharmony_ci                args, kwargs, dyn_args, dyn_kwargs = self.parse_call_args()
933e31aef6aSopenharmony_ci            else:
934e31aef6aSopenharmony_ci                args = []
935e31aef6aSopenharmony_ci                kwargs = []
936e31aef6aSopenharmony_ci                dyn_args = dyn_kwargs = None
937e31aef6aSopenharmony_ci            node = nodes.Filter(
938e31aef6aSopenharmony_ci                node, name, args, kwargs, dyn_args, dyn_kwargs, lineno=token.lineno
939e31aef6aSopenharmony_ci            )
940e31aef6aSopenharmony_ci            start_inline = False
941e31aef6aSopenharmony_ci        return node
942e31aef6aSopenharmony_ci
943e31aef6aSopenharmony_ci    def parse_test(self, node: nodes.Expr) -> nodes.Expr:
944e31aef6aSopenharmony_ci        token = next(self.stream)
945e31aef6aSopenharmony_ci        if self.stream.current.test("name:not"):
946e31aef6aSopenharmony_ci            next(self.stream)
947e31aef6aSopenharmony_ci            negated = True
948e31aef6aSopenharmony_ci        else:
949e31aef6aSopenharmony_ci            negated = False
950e31aef6aSopenharmony_ci        name = self.stream.expect("name").value
951e31aef6aSopenharmony_ci        while self.stream.current.type == "dot":
952e31aef6aSopenharmony_ci            next(self.stream)
953e31aef6aSopenharmony_ci            name += "." + self.stream.expect("name").value
954e31aef6aSopenharmony_ci        dyn_args = dyn_kwargs = None
955e31aef6aSopenharmony_ci        kwargs = []
956e31aef6aSopenharmony_ci        if self.stream.current.type == "lparen":
957e31aef6aSopenharmony_ci            args, kwargs, dyn_args, dyn_kwargs = self.parse_call_args()
958e31aef6aSopenharmony_ci        elif self.stream.current.type in {
959e31aef6aSopenharmony_ci            "name",
960e31aef6aSopenharmony_ci            "string",
961e31aef6aSopenharmony_ci            "integer",
962e31aef6aSopenharmony_ci            "float",
963e31aef6aSopenharmony_ci            "lparen",
964e31aef6aSopenharmony_ci            "lbracket",
965e31aef6aSopenharmony_ci            "lbrace",
966e31aef6aSopenharmony_ci        } and not self.stream.current.test_any("name:else", "name:or", "name:and"):
967e31aef6aSopenharmony_ci            if self.stream.current.test("name:is"):
968e31aef6aSopenharmony_ci                self.fail("You cannot chain multiple tests with is")
969e31aef6aSopenharmony_ci            arg_node = self.parse_primary()
970e31aef6aSopenharmony_ci            arg_node = self.parse_postfix(arg_node)
971e31aef6aSopenharmony_ci            args = [arg_node]
972e31aef6aSopenharmony_ci        else:
973e31aef6aSopenharmony_ci            args = []
974e31aef6aSopenharmony_ci        node = nodes.Test(
975e31aef6aSopenharmony_ci            node, name, args, kwargs, dyn_args, dyn_kwargs, lineno=token.lineno
976e31aef6aSopenharmony_ci        )
977e31aef6aSopenharmony_ci        if negated:
978e31aef6aSopenharmony_ci            node = nodes.Not(node, lineno=token.lineno)
979e31aef6aSopenharmony_ci        return node
980e31aef6aSopenharmony_ci
981e31aef6aSopenharmony_ci    def subparse(
982e31aef6aSopenharmony_ci        self, end_tokens: t.Optional[t.Tuple[str, ...]] = None
983e31aef6aSopenharmony_ci    ) -> t.List[nodes.Node]:
984e31aef6aSopenharmony_ci        body: t.List[nodes.Node] = []
985e31aef6aSopenharmony_ci        data_buffer: t.List[nodes.Node] = []
986e31aef6aSopenharmony_ci        add_data = data_buffer.append
987e31aef6aSopenharmony_ci
988e31aef6aSopenharmony_ci        if end_tokens is not None:
989e31aef6aSopenharmony_ci            self._end_token_stack.append(end_tokens)
990e31aef6aSopenharmony_ci
991e31aef6aSopenharmony_ci        def flush_data() -> None:
992e31aef6aSopenharmony_ci            if data_buffer:
993e31aef6aSopenharmony_ci                lineno = data_buffer[0].lineno
994e31aef6aSopenharmony_ci                body.append(nodes.Output(data_buffer[:], lineno=lineno))
995e31aef6aSopenharmony_ci                del data_buffer[:]
996e31aef6aSopenharmony_ci
997e31aef6aSopenharmony_ci        try:
998e31aef6aSopenharmony_ci            while self.stream:
999e31aef6aSopenharmony_ci                token = self.stream.current
1000e31aef6aSopenharmony_ci                if token.type == "data":
1001e31aef6aSopenharmony_ci                    if token.value:
1002e31aef6aSopenharmony_ci                        add_data(nodes.TemplateData(token.value, lineno=token.lineno))
1003e31aef6aSopenharmony_ci                    next(self.stream)
1004e31aef6aSopenharmony_ci                elif token.type == "variable_begin":
1005e31aef6aSopenharmony_ci                    next(self.stream)
1006e31aef6aSopenharmony_ci                    add_data(self.parse_tuple(with_condexpr=True))
1007e31aef6aSopenharmony_ci                    self.stream.expect("variable_end")
1008e31aef6aSopenharmony_ci                elif token.type == "block_begin":
1009e31aef6aSopenharmony_ci                    flush_data()
1010e31aef6aSopenharmony_ci                    next(self.stream)
1011e31aef6aSopenharmony_ci                    if end_tokens is not None and self.stream.current.test_any(
1012e31aef6aSopenharmony_ci                        *end_tokens
1013e31aef6aSopenharmony_ci                    ):
1014e31aef6aSopenharmony_ci                        return body
1015e31aef6aSopenharmony_ci                    rv = self.parse_statement()
1016e31aef6aSopenharmony_ci                    if isinstance(rv, list):
1017e31aef6aSopenharmony_ci                        body.extend(rv)
1018e31aef6aSopenharmony_ci                    else:
1019e31aef6aSopenharmony_ci                        body.append(rv)
1020e31aef6aSopenharmony_ci                    self.stream.expect("block_end")
1021e31aef6aSopenharmony_ci                else:
1022e31aef6aSopenharmony_ci                    raise AssertionError("internal parsing error")
1023e31aef6aSopenharmony_ci
1024e31aef6aSopenharmony_ci            flush_data()
1025e31aef6aSopenharmony_ci        finally:
1026e31aef6aSopenharmony_ci            if end_tokens is not None:
1027e31aef6aSopenharmony_ci                self._end_token_stack.pop()
1028e31aef6aSopenharmony_ci        return body
1029e31aef6aSopenharmony_ci
1030e31aef6aSopenharmony_ci    def parse(self) -> nodes.Template:
1031e31aef6aSopenharmony_ci        """Parse the whole template into a `Template` node."""
1032e31aef6aSopenharmony_ci        result = nodes.Template(self.subparse(), lineno=1)
1033e31aef6aSopenharmony_ci        result.set_environment(self.environment)
1034e31aef6aSopenharmony_ci        return result
1035