Exercise 1: A Real {% else %} Branch — Possible Solution ==================================================================== THE EXTENDED IfNode AND parse() ------------------------------ class IfNode: def __init__(self, cond_name, if_body, else_body=None): self.cond_name = cond_name self.if_body = if_body self.else_body = else_body or [] def render(self, ctx): body = self.if_body if ctx.get(self.cond_name) else self.else_body return ''.join(node.render(ctx) for node in body) parse() now has to know WHICH terminator it stopped on -- 'endif', 'endfor', or 'else' -- so an if-block can tell whether to keep parsing a second body for the else branch. It returns a 3-tuple instead of 2: def parse(tokens, index=0): nodes = [] while index < len(tokens): token = tokens[index] if token.startswith('{{'): nodes.append(VarNode(token[2:-2].strip())) index += 1 elif token.startswith('{%'): tag = token[2:-2].strip() if tag.startswith('if '): cond_name = tag[3:].strip() if_body, index, term = parse(tokens, index + 1) else_body = [] if term == 'else': else_body, index, term = parse(tokens, index) nodes.append(IfNode(cond_name, if_body, else_body)) elif tag.startswith('for '): m = re.match(r'for (\w+) in (\w+)', tag) var, coll_name = m.group(1), m.group(2) body, index, term = parse(tokens, index + 1) nodes.append(ForNode(var, coll_name, body)) elif tag in ('endif', 'endfor'): return nodes, index + 1, tag elif tag == 'else': return nodes, index + 1, 'else' else: index += 1 else: nodes.append(TextNode(token)) index += 1 return nodes, index, None def compile_template(source): nodes, _, _ = parse(tokenize(source)) return nodes VERIFIED, REAL RESULT ------------------------------ template = '{% if logged_in %}Welcome back!{% else %}Please log in.{% endif %}' nodes = compile_template(template) render_nodes(nodes, {'logged_in': True}) -> 'Welcome back!' render_nodes(nodes, {'logged_in': False}) -> 'Please log in.' Nested inside a for loop, to confirm else-tracking doesn't get confused by a sibling loop's own endfor: mixed = '{% for user in users %}{% if user %}
  • {{ user }}
  • {% else %}
  • (anonymous)
  • {% endif %}{% endfor %}' render_nodes(compile_template(mixed), {'users': ['Ada', '', 'Grace']}) -> '
  • Ada
  • (anonymous)
  • Grace
  • ' WHY THIS WORKS AS AN ANSWER ------------------------------ It follows the chapter's own IfNode/ForNode/parse() shape exactly, adding only what's genuinely new: a way for parse() to report which tag it stopped at, since an if-block now has two real possible endings (an else it needs to keep parsing past, or an endif it doesn't). Verifying the nested if-inside-for case confirms the fix doesn't accidentally consume a sibling ForNode's own endfor as part of the if-block's else-tracking.