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