# Source code for kajiki.ir

    import re
    from itertools import chain

    from .util import flattener, gen_name, window

    def generate_python(ir):
        cur_indent = 0
        for node in flattener(ir):
            if isinstance(node, IndentNode):
                cur_indent += 4
            elif isinstance(node, DedentNode):
                cur_indent -= 4
            for line in node.py():
                yield line.indent(cur_indent)

    class Node(object):
        def __init__(self):
            self.filename = "<string>"
            self.lineno = 0

        def py(self):  # pragma no cover
            return []

        def __iter__(self):
            yield self

        def line(self, text):
            return PyLine(self.filename, self.lineno, text)

    class PassNode(Node):
        def py(self):
            # 'pass' would result in: TypeError: 'NoneType' object is not iterable
            yield self.line('yield ""')

    class HierNode(Node):
        """Base for nodes that contain an indented Python block (def, for, if etc.)"""

        def __init__(self, body):
            super().__init__()
            self.body = tuple(x for x in body if x is not None)

        def body_iter(self):
            for x in optimize(flattener(map(flattener, self.body))):
                yield x

        def __iter__(self):
            yield self
            yield IndentNode()
            for x in self.body_iter():
                yield x
            yield DedentNode()

    class IndentNode(Node):
        pass

    class DedentNode(Node):
        pass

    class TemplateNode(HierNode):
        """Represents the root Intermediate Representation node of a template.

        Iterating over this will generate the Python code for the class
        that provides all the functions that are part of the template including
        the ``__main__`` function that represents the template code itself.

        The generated class will then be passed to :meth:`kajiki.template.Template` to
        create a :class:`kajiki.template._Template` subclass that has the
        ``render`` method to render the template.
        """

        class TemplateTail(Node):
            def py(self):
                yield self.line("template = kajiki.Template(template)")

        def __init__(self, mod_py=None, defs=None):
            super().__init__(defs)
            if mod_py is None:
                mod_py = []
            if defs is None:
                defs = []
            self.mod_py = [x for x in mod_py if x is not None]

        def py(self):
            yield self.line("class template:")

        def __iter__(self):
            for x in flattener(self.mod_py):
                yield x
            for x in super().__iter__():
                yield x
            yield self.TemplateTail()

    class ImportNode(Node):
        def __init__(self, tpl_name, alias=None):
            super().__init__()
            self.tpl_name = tpl_name
            self.alias = alias

        def py(self):
            yield self.line(
                "local.__kj__.import_(%r, %r, self.__globals__)"
                % (self.tpl_name, self.alias)
            )

    class IncludeNode(Node):
        def __init__(self, tpl_name):
            super().__init__()
            self.tpl_name = tpl_name

        def py(self):
            yield self.line(
                "yield local.__kj__.import_(%r, None, self.__globals__).__main__()"
                % (self.tpl_name)
            )

    class ExtendNode(Node):
        def __init__(self, tpl_name):
            super().__init__()
            self.tpl_name = tpl_name

        def py(self):
            yield self.line("yield local.__kj__.extend(%r).__main__()" % (self.tpl_name))

    class DefNode(HierNode):
        prefix = "@kajiki.expose"

        def __init__(self, decl, *body):
            super().__init__(body)
            self.decl = decl

        def py(self):
            yield self.line(self.prefix)
            yield self.line("def %s:" % (self.decl))

        def __iter__(self):
            yield self
            yield IndentNode()
            is_empty = True
            for x in self.body_iter():
                yield x
                is_empty = False
            if is_empty:  # In Python, a function without a body is a SyntaxError.
                yield PassNode()  # Prevent creation of a function without a body
            yield DedentNode()

    class InnerDefNode(DefNode):
        prefix = "@__kj__.flattener.decorate"

    class CallNode(HierNode):
        class CallTail(Node):
            def __init__(self, call):
                super().__init__()
                self.call = call

            def py(self):
                yield self.line("yield " + self.call)

        def __init__(self, caller, callee, *body):
            super().__init__(body)
            fname = gen_name()
            self.decl = caller.replace("$caller", fname)
            self.call = callee.replace("$caller", fname)

        def py(self):
            yield self.line("@__kj__.flattener.decorate")
            yield self.line("def %s:" % (self.decl))

        def __iter__(self):
            yield self
            yield IndentNode()
            for x in self.body_iter():
                yield x
            yield DedentNode()
            yield self.CallTail(self.call)

    class ForNode(HierNode):
        def __init__(self, decl, *body):
            super().__init__(body)
            self.decl = decl

        def py(self):
            yield self.line("for %s:" % (self.decl))

    class WithNode(HierNode):

        assignment_pattern = re.compile(r"(?:^|;)\s*([^;=]+)=(?!=)", re.M)

        class WithTail(Node):
            def __init__(self, var_names):
                super().__init__()
                self.var_names = var_names

            def py(self):
                yield self.line(
                    "(%s,) = local.__kj__.pop_with()" % (",".join(self.var_names),)
                )
                # yield self.line('if %s == (): del %s' % (v, v))

        def __init__(self, vars, *body):
            super().__init__(body)
            assignments = []
            matches = self.assignment_pattern.finditer(vars)
            for m1, m2 in window(chain(matches, [None]), 2):
                lhs = m1.group(1).strip()
                rhs = vars[m1.end() : (m2.start() if m2 else len(vars))]
                assignments.append((lhs, rhs))

            self.vars = assignments
            self.var_names = [lhs for lhs, _ in assignments]

        def py(self):
            yield self.line(
                "local.__kj__.push_with(locals(), [%s])"
                % (",".join('"%s"' % k for k in self.var_names),)
            )
            for k, v in self.vars:
                yield self.line("%s = %s" % (k, v))

        def __iter__(self):
            yield self
            for x in self.body_iter():
                yield x
            yield self.WithTail(self.var_names)

    class SwitchNode(HierNode):
        class SwitchTail(Node):
            def py(self):
                yield self.line("local.__kj__.pop_switch()")

        def __init__(self, decl, *body):
            super().__init__(body)
            self.decl = decl

        def py(self):
            yield self.line("local.__kj__.push_switch(%s)" % self.decl)
            yield self.line("if False: pass")

        def __iter__(self):
            yield self
            for x in self.body_iter():
                yield x
            yield self.SwitchTail()

    class CaseNode(HierNode):
        def __init__(self, decl, *body):
            super().__init__(body)
            self.decl = decl

        def py(self):
            yield self.line("elif local.__kj__.case(%s):" % self.decl)

    class IfNode(HierNode):
        def __init__(self, decl, *body):
            super().__init__(body)
            self.decl = decl

        def py(self):
            yield self.line("if %s:" % self.decl)

    class ElseNode(HierNode):
        def __init__(self, *body):
            super().__init__(body)

        def py(self):
            yield self.line("else:")

    class TextNode(Node):
        """Node that outputs Python literals."""

        def __init__(self, text, guard=None):
            super().__init__()
            self.text = text
            self.guard = guard

        def py(self):
            s = "yield %r" % self.text
            if self.guard:
                yield self.line("if %s: %s" % (self.guard, s))
            else:
                yield self.line(s)

    class TranslatableTextNode(TextNode):
        def py(self):
            text = self.text.strip()
            if text:
                s = "yield local.__kj__.gettext(%r)" % self.text
            else:
                s = "yield %r" % self.text
            if self.guard:
                yield self.line("if %s: %s" % (self.guard, s))
            else:
                yield self.line(s)

    class ExprNode(Node):
        """Node that contains a Python expression to be evaluated when the template
        is executed.
        """

        def __init__(self, text, safe=False):
            super().__init__()
            self.text = text
            self.safe = safe

        def py(self):
            if self.safe:
                yield self.line("yield %s" % self.text)
            else:
                yield self.line("yield self.__kj__.escape(%s)" % self.text)

    class AttrNode(HierNode):
        """Node that renders HTML/XML attributes."""

        class AttrTail(Node):
            def __init__(self, parent):
                super().__init__()
                self.p = parent

            def py(self):
                gen = self.p.genname
                x = gen_name()
                yield self.line("%s = self.__kj__.collect(%s())" % (gen, gen))
                yield self.line(
                    "for %s in self.__kj__.render_attrs({%r:%s}, %r):"
                    % (x, self.p.attr, gen, self.p.mode)
                )
                yield self.line("    yield %s" % x)

        def __init__(self, attr, value, guard=None, mode="xml"):
            super().__init__(value)
            self.attr = attr
            self.guard = guard
            self.mode = mode
            self.genname = gen_name()

        def py(self):
            yield self.line("def %s():" % self.genname)

        def __iter__(self):
            if self.guard:
                new_body = IfNode(
                    self.guard, AttrNode(self.attr, value=self.body, mode=self.mode)
                )
                for x in new_body:
                    yield x
            else:
                yield self
                yield IndentNode()
                if self.body:
                    for part in self.body_iter():
                        yield part
                else:
                    yield TextNode("")
                yield DedentNode()
                yield self.AttrTail(self)

    class AttrsNode(Node):
        def __init__(self, attrs, guard=None, mode="xml"):
            super().__init__()
            self.attrs = attrs
            self.guard = guard
            self.mode = mode

        def py(self):
            x = gen_name()

            def _body():
                yield self.line(
                    "for %s in self.__kj__.render_attrs(%s, %r):"
                    % (x, self.attrs, self.mode)
                )
                yield self.line("    yield %s" % x)

            if self.guard:
                yield self.line("if %s:" % self.guard)
                for line in _body():
                    yield line.indent()
            else:
                for line in _body():
                    yield line

    class PythonNode(Node):
        def __init__(self, *body):
            super().__init__()
            self.module_level = False
            blocks = []
            for b in body:
                assert isinstance(b, TextNode)
                blocks.append(b.text)
            text = "".join(blocks)
            if text[0] == "%":
                self.module_level = True
                text = text[1:]
            self.lines = list(self._normalize(text))

        def py(self):
            for line in self.lines:
                yield self.line(line)

        def _normalize(self, text):
            if text.startswith("#\n"):
                text = text[2:]
            prefix = None
            for line in text.splitlines():
                if prefix is None:
                    rest = line.lstrip()
                    prefix = line[: len(line) - len(rest)]
                assert line.startswith(prefix)
                yield line[len(prefix) :]

    def optimize(iter_node):
        last_node = None
        for node in iter_node:
            if (
                type(node) == TextNode
                and type(last_node) == TextNode
                and last_node.guard == node.guard
            ):
                last_node.text += node.text
                # Erase this node by not yielding it.
                continue
            if last_node is not None:
                yield last_node
            last_node = node
        if last_node is not None:
            yield last_node

    class PyLine(object):
        def __init__(self, filename, lineno, text, indent=0):
            self._filename = filename
            self._lineno = lineno
            self._text = text
            self._indent = indent

        def indent(self, sz=4):
            return PyLine(self._filename, self._lineno, self._text, self._indent + sz)

        def __str__(self):
            return (" " * self._indent) + self._text
            if self._lineno:
                return (
                    (" " * self._indent)
                    + self._text
                    + "\t# %s:%d" % (self._filename, self._lineno)
                )
            else:
                return (" " * self._indent) + self._text

        def __repr__(self):
            return "%s:%s %s" % (self._filename, self._lineno, self)