mirror of
				https://github.com/gonum/gonum.git
				synced 2025-10-31 18:42:45 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			291 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			291 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright ©2017 The gonum Authors. All rights reserved.
 | |
| // Use of this source code is governed by a BSD-style
 | |
| // license that can be found in the LICENSE file.
 | |
| 
 | |
| package dot
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 
 | |
| 	"gonum.org/v1/gonum/graph"
 | |
| 	"gonum.org/v1/gonum/graph/formats/dot"
 | |
| 	"gonum.org/v1/gonum/graph/formats/dot/ast"
 | |
| 	"gonum.org/v1/gonum/graph/internal/set"
 | |
| )
 | |
| 
 | |
| // Builder is a graph that can have user-defined nodes and edges added.
 | |
| type Builder interface {
 | |
| 	graph.Graph
 | |
| 	graph.Builder
 | |
| 	// NewEdge adds a new edge from the source to the destination node to the
 | |
| 	// graph, or returns the existing edge if already present.
 | |
| 	NewEdge(from, to graph.Node) graph.Edge
 | |
| }
 | |
| 
 | |
| // UnmashalerAttrs is implemented by graph values that can unmarshal global
 | |
| // DOT attributes.
 | |
| type UnmarshalerAttrs interface {
 | |
| 	// DOTUnmarshalerAttrs returns the global attribute unmarshalers.
 | |
| 	DOTUnmarshalerAttrs() (graph, node, edge UnmarshalerAttr)
 | |
| }
 | |
| 
 | |
| // UnmarshalerAttr is implemented by types that can unmarshal a DOT
 | |
| // attribute description of themselves.
 | |
| type UnmarshalerAttr interface {
 | |
| 	// UnmarshalDOTAttr decodes a single DOT attribute.
 | |
| 	UnmarshalDOTAttr(attr Attribute) error
 | |
| }
 | |
| 
 | |
| // UnmarshalerID is implemented by types that can unmarshal a DOT ID.
 | |
| type UnmarshalerID interface {
 | |
| 	// UnmarshalDOTID decodes a single DOT ID.
 | |
| 	UnmarshalDOTID(id string)
 | |
| }
 | |
| 
 | |
| // Unmarshal parses the Graphviz DOT-encoded data and stores the result in dst.
 | |
| func Unmarshal(data []byte, dst Builder) error {
 | |
| 	file, err := dot.ParseBytes(data)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	if len(file.Graphs) != 1 {
 | |
| 		return fmt.Errorf("invalid number of graphs; expected 1, got %d", len(file.Graphs))
 | |
| 	}
 | |
| 	return copyGraph(dst, file.Graphs[0])
 | |
| }
 | |
| 
 | |
| // copyGraph copies the nodes and edges from the Graphviz AST source graph to
 | |
| // the destination graph. Edge direction is maintained if present.
 | |
| func copyGraph(dst Builder, src *ast.Graph) (err error) {
 | |
| 	defer func() {
 | |
| 		switch e := recover().(type) {
 | |
| 		case nil:
 | |
| 		case error:
 | |
| 			err = e
 | |
| 		default:
 | |
| 			panic(e)
 | |
| 		}
 | |
| 	}()
 | |
| 	gen := &generator{
 | |
| 		directed: src.Directed,
 | |
| 		ids:      make(map[string]graph.Node),
 | |
| 	}
 | |
| 	if a, ok := dst.(UnmarshalerAttrs); ok {
 | |
| 		gen.graphAttr, gen.nodeAttr, gen.edgeAttr = a.DOTUnmarshalerAttrs()
 | |
| 	}
 | |
| 	for _, stmt := range src.Stmts {
 | |
| 		gen.addStmt(dst, stmt)
 | |
| 	}
 | |
| 	return err
 | |
| }
 | |
| 
 | |
| // A generator keeps track of the information required for generating a gonum
 | |
| // graph from a dot AST graph.
 | |
| type generator struct {
 | |
| 	// Directed graph.
 | |
| 	directed bool
 | |
| 	// Map from dot AST node ID to gonum node.
 | |
| 	ids map[string]graph.Node
 | |
| 	// Nodes processed within the context of a subgraph, that is to be used as a
 | |
| 	// vertex of an edge.
 | |
| 	subNodes []graph.Node
 | |
| 	// Stack of start indices into the subgraph node slice. The top element
 | |
| 	// corresponds to the start index of the active (or inner-most) subgraph.
 | |
| 	subStart []int
 | |
| 	// graphAttr, nodeAttr and edgeAttr are global graph attributes.
 | |
| 	graphAttr, nodeAttr, edgeAttr UnmarshalerAttr
 | |
| }
 | |
| 
 | |
| // node returns the gonum node corresponding to the given dot AST node ID,
 | |
| // generating a new such node if none exist.
 | |
| func (gen *generator) node(dst Builder, id string) graph.Node {
 | |
| 	if n, ok := gen.ids[id]; ok {
 | |
| 		return n
 | |
| 	}
 | |
| 	n := dst.NewNode()
 | |
| 	if n, ok := n.(UnmarshalerID); ok {
 | |
| 		n.UnmarshalDOTID(id)
 | |
| 	}
 | |
| 	gen.ids[id] = n
 | |
| 	// Check if within the context of a subgraph, that is to be used as a vertex
 | |
| 	// of an edge.
 | |
| 	if gen.isInSubgraph() {
 | |
| 		// Append node processed within the context of a subgraph, that is to be
 | |
| 		// used as a vertex of an edge
 | |
| 		gen.appendSubgraphNode(n)
 | |
| 	}
 | |
| 	return n
 | |
| }
 | |
| 
 | |
| // addStmt adds the given statement to the graph.
 | |
| func (gen *generator) addStmt(dst Builder, stmt ast.Stmt) {
 | |
| 	switch stmt := stmt.(type) {
 | |
| 	case *ast.NodeStmt:
 | |
| 		n, ok := gen.node(dst, stmt.Node.ID).(UnmarshalerAttr)
 | |
| 		if !ok {
 | |
| 			return
 | |
| 		}
 | |
| 		for _, attr := range stmt.Attrs {
 | |
| 			a := Attribute{
 | |
| 				Key:   attr.Key,
 | |
| 				Value: attr.Val,
 | |
| 			}
 | |
| 			if err := n.UnmarshalDOTAttr(a); err != nil {
 | |
| 				panic(fmt.Errorf("unable to unmarshal node DOT attribute (%s=%s)", a.Key, a.Value))
 | |
| 			}
 | |
| 		}
 | |
| 	case *ast.EdgeStmt:
 | |
| 		gen.addEdgeStmt(dst, stmt)
 | |
| 	case *ast.AttrStmt:
 | |
| 		var n UnmarshalerAttr
 | |
| 		var dst string
 | |
| 		switch stmt.Kind {
 | |
| 		case ast.KindGraph:
 | |
| 			if gen.graphAttr == nil {
 | |
| 				return
 | |
| 			}
 | |
| 			n = gen.graphAttr
 | |
| 			dst = "graph"
 | |
| 		case ast.KindNode:
 | |
| 			if gen.nodeAttr == nil {
 | |
| 				return
 | |
| 			}
 | |
| 			n = gen.nodeAttr
 | |
| 			dst = "node"
 | |
| 		case ast.KindEdge:
 | |
| 			if gen.edgeAttr == nil {
 | |
| 				return
 | |
| 			}
 | |
| 			n = gen.edgeAttr
 | |
| 			dst = "edge"
 | |
| 		default:
 | |
| 			panic("unreachable")
 | |
| 		}
 | |
| 		for _, attr := range stmt.Attrs {
 | |
| 			a := Attribute{
 | |
| 				Key:   attr.Key,
 | |
| 				Value: attr.Val,
 | |
| 			}
 | |
| 			if err := n.UnmarshalDOTAttr(a); err != nil {
 | |
| 				panic(fmt.Errorf("unable to unmarshal global %s DOT attribute (%s=%s)", dst, a.Key, a.Value))
 | |
| 			}
 | |
| 		}
 | |
| 	case *ast.Attr:
 | |
| 		// ignore.
 | |
| 	case *ast.Subgraph:
 | |
| 		for _, stmt := range stmt.Stmts {
 | |
| 			gen.addStmt(dst, stmt)
 | |
| 		}
 | |
| 	default:
 | |
| 		panic(fmt.Sprintf("unknown statement type %T", stmt))
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // addEdgeStmt adds the given edge statement to the graph.
 | |
| func (gen *generator) addEdgeStmt(dst Builder, e *ast.EdgeStmt) {
 | |
| 	fs := gen.addVertex(dst, e.From)
 | |
| 	ts := gen.addEdge(dst, e.To)
 | |
| 	for _, f := range fs {
 | |
| 		for _, t := range ts {
 | |
| 			edge, ok := dst.NewEdge(f, t).(UnmarshalerAttr)
 | |
| 			if !ok {
 | |
| 				continue
 | |
| 			}
 | |
| 			for _, attr := range e.Attrs {
 | |
| 				a := Attribute{
 | |
| 					Key:   attr.Key,
 | |
| 					Value: attr.Val,
 | |
| 				}
 | |
| 				if err := edge.UnmarshalDOTAttr(a); err != nil {
 | |
| 					panic(fmt.Errorf("unable to unmarshal edge DOT attribute (%s=%s)", a.Key, a.Value))
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // addVertex adds the given vertex to the graph, and returns its set of nodes.
 | |
| func (gen *generator) addVertex(dst Builder, v ast.Vertex) []graph.Node {
 | |
| 	switch v := v.(type) {
 | |
| 	case *ast.Node:
 | |
| 		n := gen.node(dst, v.ID)
 | |
| 		return []graph.Node{n}
 | |
| 	case *ast.Subgraph:
 | |
| 		gen.pushSubgraph()
 | |
| 		for _, stmt := range v.Stmts {
 | |
| 			gen.addStmt(dst, stmt)
 | |
| 		}
 | |
| 		return gen.popSubgraph()
 | |
| 	default:
 | |
| 		panic(fmt.Sprintf("unknown vertex type %T", v))
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // addEdge adds the given edge to the graph, and returns its set of nodes.
 | |
| func (gen *generator) addEdge(dst Builder, to *ast.Edge) []graph.Node {
 | |
| 	if !gen.directed && to.Directed {
 | |
| 		panic(fmt.Errorf("directed edge to %v in undirected graph", to.Vertex))
 | |
| 	}
 | |
| 	fs := gen.addVertex(dst, to.Vertex)
 | |
| 	if to.To != nil {
 | |
| 		ts := gen.addEdge(dst, to.To)
 | |
| 		for _, f := range fs {
 | |
| 			for _, t := range ts {
 | |
| 				dst.NewEdge(f, t)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	return fs
 | |
| }
 | |
| 
 | |
| // pushSubgraph pushes the node start index of the active subgraph onto the
 | |
| // stack.
 | |
| func (gen *generator) pushSubgraph() {
 | |
| 	gen.subStart = append(gen.subStart, len(gen.subNodes))
 | |
| }
 | |
| 
 | |
| // popSubgraph pops the node start index of the active subgraph from the stack,
 | |
| // and returns the nodes processed since.
 | |
| func (gen *generator) popSubgraph() []graph.Node {
 | |
| 	// Get nodes processed since the subgraph became active.
 | |
| 	start := gen.subStart[len(gen.subStart)-1]
 | |
| 	// TODO: Figure out a better way to store subgraph nodes, so that duplicates
 | |
| 	// may not occur.
 | |
| 	nodes := unique(gen.subNodes[start:])
 | |
| 	// Remove subgraph from stack.
 | |
| 	gen.subStart = gen.subStart[:len(gen.subStart)-1]
 | |
| 	if len(gen.subStart) == 0 {
 | |
| 		// Remove subgraph nodes when the bottom-most subgraph has been processed.
 | |
| 		gen.subNodes = gen.subNodes[:0]
 | |
| 	}
 | |
| 	return nodes
 | |
| }
 | |
| 
 | |
| // unique returns the set of unique nodes contained within ns.
 | |
| func unique(ns []graph.Node) []graph.Node {
 | |
| 	var nodes []graph.Node
 | |
| 	seen := make(set.Int64s)
 | |
| 	for _, n := range ns {
 | |
| 		id := n.ID()
 | |
| 		if seen.Has(id) {
 | |
| 			// skip duplicate node
 | |
| 			continue
 | |
| 		}
 | |
| 		seen.Add(id)
 | |
| 		nodes = append(nodes, n)
 | |
| 	}
 | |
| 	return nodes
 | |
| }
 | |
| 
 | |
| // isInSubgraph reports whether the active context is within a subgraph, that is
 | |
| // to be used as a vertex of an edge.
 | |
| func (gen *generator) isInSubgraph() bool {
 | |
| 	return len(gen.subStart) > 0
 | |
| }
 | |
| 
 | |
| // appendSubgraphNode appends the given node to the slice of nodes processed
 | |
| // within the context of a subgraph.
 | |
| func (gen *generator) appendSubgraphNode(n graph.Node) {
 | |
| 	gen.subNodes = append(gen.subNodes, n)
 | |
| }
 | 
