class EXPRESSION:
    pp_count = 0

    def __init__(self):
        self.pp = EXPRESSION.pp_count
        EXPRESSION.pp_count += 1

    @staticmethod
    def copy():
        return EXPRESSION()

    # Returns a list of tuples (edge_name, child_expression)
    def children(self):
        out = []
        for key, value in self.__dict__.items():
            if key == "pp":
                continue
            if isinstance(value, EXPRESSION):
                out.append((key, value))
            elif isinstance(value, list):
                for i, elem in enumerate(value):
                    if isinstance(elem, EXPRESSION):
                        out.append((f"{key}{i}", elem))
        return out

    # Export AST to dot format
    def to_dot(self, visited=None, root=True):
        if visited is None:
            visited = set()

        # Prevent infinite recursion
        if id(self) in visited:
            return ""
        visited.add(id(self))

        parts = []

        # Add a header at the root node
        if root:
            parts.append("digraph AST {\n")

        # Append to label
        label = type(self).__name__
        if hasattr(self, "operator"):
            label += f"({self.operator})"
        if hasattr(self, "name"):
            label += f"({self.name})"
        if hasattr(self, "value"):
            label += f"({self.value})"

        parts.append(f'  node{self.pp} [label="{label}"];\n')

        # Draw edges
        for edge_name, child in self.children():
            parts.append(f'  node{self.pp} -> node{child.pp} [label="{edge_name}"];\n')
            parts.append(child.to_dot(visited, root=False))

        # Add footer at the root node
        if root:
            parts.append("}\n")

        return "".join(parts)

class LET(EXPRESSION):
    def __init__(self, decls, body):
        super().__init__()
        self.decl = decls
        self.body = body

    def __str__(self):
        return "let " + ", ".join(str(d) for d in self.decl) + " in " + str(self.body)

class DECL(EXPRESSION):
    def __init__(self, f_name, params, body):
        super().__init__()
        self.f_name = f_name
        self.params = params
        self.body = body

    def __str__(self):
        return f"{self.f_name}({self.params}) {{ {self.body} }}"

class CALL(EXPRESSION):
    def __init__(self, f_name, args):
        super().__init__()
        self.f_name = f_name
        self.arg = args

    def __str__(self):
        return self.f_name + "(" + ",".join(str(a) for a in self.arg) + ")"

class ID(EXPRESSION):
    def __init__(self, name):
        super().__init__()
        self.name = name

    def __str__(self):
        return self.name

class CONST(EXPRESSION):
    def __init__(self, value):
        super().__init__()
        self.value = value

    def __str__(self):
        return str(self.value)

class AOP(EXPRESSION):
    def __init__(self, operator, arg1, arg2):
        super().__init__()
        self.operator = operator
        self.arg1 = arg1
        self.arg2 = arg2

    def __str__(self):
        return "(" + str(self.arg1) + " " + str(self.operator) + " " + str(self.arg2) + ")"

class EQOP(EXPRESSION):
    def __init__(self, operator, arg1, arg2):
        super().__init__()
        self.operator = operator
        self.arg1 = arg1
        self.arg2 = arg2

    def __str__(self):
        return "(" + str(self.arg1) + " " + str(self.operator) + " " + str(self.arg2) + ")"

class COMP(EXPRESSION):
    def __init__(self, operator, arg1, arg2):
        super().__init__()
        self.operator = operator
        self.arg1 = arg1
        self.arg2 = arg2

    def __str__(self):
        return "(" + str(self.arg1) + " " + str(self.operator) + " " + str(self.arg2) + ")"

class LOP(EXPRESSION):
    def __init__(self, operator, arg1, arg2):
        super().__init__()
        self.operator = operator
        self.arg1 = arg1
        self.arg2 = arg2

    def __str__(self):
        return "(" + str(self.arg1) + " " + str(self.operator) + " " + str(self.arg2) + ")"

class ASSIGN(EXPRESSION):
    def __init__(self, var, expr):
        super().__init__()
        self.var = var
        self.expr = expr

    def __str__(self):
        return self.var.name + " = " + str(self.expr)

class SEQ(EXPRESSION):
    def __init__(self, exp1, exp2):
        super().__init__()
        self.exp1 = exp1
        self.exp2 = exp2

    def __str__(self):
        return str(self.exp1) + "; " + str(self.exp2)

class IF(EXPRESSION):
    def __init__(self, cond, exp1, exp2):
        super().__init__()
        self.cond = cond
        self.exp1 = exp1
        self.exp2 = exp2

    def __str__(self):
        return "if (" + str(self.cond) + ") then { " + str(self.exp1) + " } else { " + str(self.exp2) + " }"

class WHILE(EXPRESSION):
    def __init__(self, cond, body):
        super().__init__()
        self.cond = cond
        self.body = body

    def __str__(self):
        return "while (" + str(self.cond) + ") do { " + str(self.body) + " }"