Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Not all "normal languages" (or even "dynamic scripting languages") are created equal. While I wouldn't want to aim for something equivalent to the whole d2 syntax as an internal DSL in Ruby, if you wanted to just create edges with optional labels with a syntax where "bare" edges are:

   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.


Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: