#πŸ”’ converting AST to graph syntax

17 messages Β· Page 1 of 1 (latest)

west marsh
#

i want to construct a .dot graph (graphviz) to be able to visualise my AST for debugging purposes. here are my nodes so far: ```py
class Constant:
value: Any

class Variable:
name: str

class BinOp:
left: Node
op: str
right: Node

class UnOp:
op: str
node: Node


what's the best way to go about this? for example, parsing `(2x + 3) * 5 + 5 + 2` outputs: ```yml
<BinOp op='+' left=<BinOp op='*' left=<BinOp op='+' left=<BinOp op='*' left=<Constant value='2'> right=<Variable name='x'>> right=<Constant value='3'>> right=<Constant value='5'>> right=<Constant value='7'>>

which would be:

      Add
      / \
     Mul 7 
     / \
   Add  5
   / \
 Mul  3
 / \
2  Var
    |
    x

however i dont know how to put this into .dot syntax reliably. any ideas?

gritty flintBOT
#

@west marsh

Python help channel opened

Remember to:

  • Ask your Python question, not if you can ask or if there's an expert who can help.
  • Show a code sample as text (rather than a screenshot) and the error message, if you've got one.
  • Explain what you expect to happen and what actually happens.

:warning: Do not pip install anything that isn't related to your question, especially if asked to over DMs.

agile thicket
tiny plover
#

I think you should try to break this down into a simpler problem, instead of trying to solve it all at once

#

Starting from the bottom, I'm going to ignore the Variable node since that's a bit of an edgecase, however I'd create a class for displaying a node, with an attribute for left padding. so for the multiply node towards the bottom, that would have a padding of 0, as you work your way up add the padding required from the previous node

#

Only issue I could see arising is if where you have the constants say the 7, if that could be another operation tree

#

or im dumb actually and that wasn't what you're asking

spiral walrus
#

i think this should involve just writing the node, and for each children setting the edge and recursively doing the graph

#

so, like

def walk(node):
  new(node)
  for child in node.children:
    edge(node, child)
    walk(child)

ill try to see what the graphviz format is and see if i can cook it

west marsh
#

i got it working

#
def recursively_get_dot(root: Node, counts: dict[type[Node], int] = DEFAULT_COUNTS) -> str:
    if isinstance(root, Constant):
        counts[Constant] += 1
        c = counts[Constant]
        
        return f"const{c}[label=\"{root.value}\"]"
    
    elif isinstance(root, Variable):
        counts[Variable] += 1
        c = counts[Variable]

        return f"var{c}[label=\"{root.name}\"]"

    elif isinstance(root, BinOp):
        L = recursively_get_dot(root.left, counts)
        R = recursively_get_dot(root.right, counts)

        counts[BinOp] += 1
        c = counts[BinOp]

        extra = []

        if isinstance(root.left, Variable) or isinstance(root.right, Variable):
            extra += [f"op{c} -> var{counts[Variable]}"]
        
        if isinstance(root.left, Constant) or isinstance(root.right, Constant):
            extra += [f"op{c} -> const{counts[Constant]}"]
        
        return '\n'.join([
            f"op{c}[label=\"{root.op}\"]",
            L,
            R +
            ('\n' f'op{c} -> op{c - 1}' if c > 1 else ''),
            *extra
        ])

    elif isinstance(root, UnOp):
        counts[UnOp] += 1
        c = counts[UnOp]

        return '\n'.join([
            f"op[label={root.op!r}]",
            recursively_get_dot(root.node, counts)
        ])
    
    else:
        raise TypeError(f"unrecognised node type {type(root)!r}")
spiral walrus
#

hmh, what renderer are you using for it?

#

!e

from collections.abc import Sequence
from dataclasses import dataclass

class Node:
    def label(self) -> object: return repr(self)
    def children(self) -> "Sequence[Node]": return ()

@dataclass
class Constant(Node):
    value: object
    def label(self): return self.value

@dataclass
class Variable(Node):
    name: str
    def label(self): return self.name

@dataclass
class BinOp(Node):
    left: Node
    op: str
    right: Node
    def label(self): return self.op
    def children(self): return (self.left, self.right)

@dataclass
class UnOp(Node):
    op: str
    node: Node
    def label(self): return self.op
    def children(self): return (self.node,)

def graph(root: Node) -> list[str]:
    buffer: list[str] = ["digraph AST {"]
    def new(node: Node) -> None:
        buffer.append(f'    {id(node)} [label="{node.label()}"];')
    def edge(start: Node, end: Node) -> None:
        buffer.append(f'    {id(start)} -> {id(end)};')
    def walk(node: Node) -> None:
        new(node)
        for child in node.children():
            edge(node, child)
            walk(child)
    walk(root)
    buffer.append("}")
    return buffer

ast = BinOp(
    BinOp(
        Variable("x"),
        "*",
        Constant(42),
    ),
    "/",
    UnOp(
        "-",
        Constant(42),
    )    
)
print('\n'.join(graph(ast)))
gritty flintBOT
gritty flintBOT
#
Python help channel closed for inactivity

This help channel has been closed. Feel free to create a new post in #1035199133436354600. To maximize your chances of getting a response, check out this guide on asking good questions.