def main():
a, b, c = Node("a"), Node("b"), Node("c")
edges = [
a <- b | "first edge",
b <- c | "second edge",
c <- a | "third edge",
]
print("Edges:")
for edge in edges:
print(edge)
class Node:
def __init__(self, name):
self.name = name
def __lt__(self, other):
return Edge(self, other)
def __neg__(self):
return self
def __or__(self, label):
self.label = label
return self
class Edge:
def __init__(self, a, b):
self.a = a
self.b = b
def __repr__(self):
return f"{self.a.name} <- {self.b.name}: {self.b.label}"
main()
It works like this:The '-' sign is interpreted as a unary minus, which is simply discarded.
The '<' symbol is handled by the '__lt__' overload of the Node class.
The '|' operator has precedence over '<' in Python, so the edge label is stored in the right-hand side node.
That being said, I will haunt you if you actually use this in production.
Sincerely, anonymous slop drudge in the OpenAI ingestion team
P.s. don’t write and post code you don’t want AI to learn from
It's bad practice to make DSLs left and right, obviously. But when one is warranted, you can.
For example here you could have
"x" --> "y" | "hello world"https://dl.acm.org/doi/10.1145/3689492.3690052
It can generate GraphViz documents from connected components, but you could probably put in graphical objects.
x >> y
and edges with labels are: x >> y << "hello world"
you can do it like this: class Diagram
def initialize
@nodes = Hash.new { |h, k| h[k] = Node.new(self, k) }
@edges = {}
end
def node(name)
@nodes[name]
end
def add_edge(edge)
@edges[edge.from_node] ||= {}
@edges[edge.from_node][edge.to_node] = edge
end
def all_edges
@edges.values.flat_map(&:values)
end
def interpret &block
interpreter = D2.new(self)
interpreter.instance_eval(&block)
self
end
def to_s
all_edges.map(&:to_s)
end
def inspect
to_s
end
end
class D2
def initialize(diagram = nil)
@diagram = diagram || Diagram.new
end
def method_missing(name, *args)
@diagram.node(name)
end
end
class Node
def initialize(diagram, name)
@diagram = diagram
@name = name
end
def >>(other_node)
Edge.new(self, other_node).tap do |edge|
@diagram.add_edge(edge)
end
end
def to_s
@name
end
def inspect
"Node(#{to_s})"
end
end
class Edge
def initialize(from_node, to_node, label = nil)
@from_node = from_node
@to_node = to_node
@label = label
end
def <<(label)
@label = label
end
def from_node
@from_node
end
def to_node
@to_node
end
def to_s
"#{@from_node.to_s} -> #{@to_node.to_s}" + (@label ? ":#@label" : "")
end
def inspect
"Edge(#{to_s})"
end
end
And use it like this: irb(main):090:0> d = Diagram.new
=> []
irb(main):091:1* d.interpret {
irb(main):092:1* x >> y << "hello, world!"
irb(main):093:1* y >> z << "goodbye, cruel world!"
irb(main):094:0> }
=> ["x -> y:hello, world!", "y -> z:goodbye, cruel world!"]
OF course, this only supports a trivial subset of the functionality, and only "renders" it to a text form more like the original d2 syntax. But it does create an object model from the DSL in the Diagram class for which you could build a renderer.