github.com/gopherd/gonum@v0.0.4/graph/formats/dot/internal/astx/astx.go (about)

     1  // This file is dual licensed under CC0 and The Gonum License.
     2  //
     3  // Copyright ©2017 The Gonum Authors. All rights reserved.
     4  // Use of this source code is governed by a BSD-style
     5  // license that can be found in the LICENSE file.
     6  //
     7  // Copyright ©2017 Robin Eklind.
     8  // This file is made available under a Creative Commons CC0 1.0
     9  // Universal Public Domain Dedication.
    10  
    11  package astx
    12  
    13  import (
    14  	"fmt"
    15  	"strings"
    16  
    17  	"github.com/gopherd/gonum/graph/formats/dot/ast"
    18  	"github.com/gopherd/gonum/graph/formats/dot/internal/token"
    19  )
    20  
    21  // === [ File ] ================================================================
    22  
    23  // NewFile returns a new file based on the given graph.
    24  func NewFile(graph interface{}) (*ast.File, error) {
    25  	g, ok := graph.(*ast.Graph)
    26  	if !ok {
    27  		return nil, fmt.Errorf("invalid graph type; expected *ast.Graph, got %T", graph)
    28  	}
    29  	return &ast.File{Graphs: []*ast.Graph{g}}, nil
    30  }
    31  
    32  // AppendGraph appends graph to the given file.
    33  func AppendGraph(file, graph interface{}) (*ast.File, error) {
    34  	f, ok := file.(*ast.File)
    35  	if !ok {
    36  		return nil, fmt.Errorf("invalid file type; expected *ast.File, got %T", file)
    37  	}
    38  	g, ok := graph.(*ast.Graph)
    39  	if !ok {
    40  		return nil, fmt.Errorf("invalid graph type; expected *ast.Graph, got %T", graph)
    41  	}
    42  	f.Graphs = append(f.Graphs, g)
    43  	return f, nil
    44  }
    45  
    46  // === [ Graphs ] ==============================================================
    47  
    48  // NewGraph returns a new graph based on the given graph strictness, direction,
    49  // optional ID and optional statements.
    50  func NewGraph(strict, directed, optID, optStmts interface{}) (*ast.Graph, error) {
    51  	s, ok := strict.(bool)
    52  	if !ok {
    53  		return nil, fmt.Errorf("invalid strictness type; expected bool, got %T", strict)
    54  	}
    55  	d, ok := directed.(bool)
    56  	if !ok {
    57  		return nil, fmt.Errorf("invalid direction type; expected bool, got %T", directed)
    58  	}
    59  	id, ok := optID.(string)
    60  	if optID != nil && !ok {
    61  		return nil, fmt.Errorf("invalid ID type; expected string or nil, got %T", optID)
    62  	}
    63  	stmts, ok := optStmts.([]ast.Stmt)
    64  	if optStmts != nil && !ok {
    65  		return nil, fmt.Errorf("invalid statements type; expected []ast.Stmt or nil, got %T", optStmts)
    66  	}
    67  	return &ast.Graph{Strict: s, Directed: d, ID: id, Stmts: stmts}, nil
    68  }
    69  
    70  // === [ Statements ] ==========================================================
    71  
    72  // NewStmtList returns a new statement list based on the given statement.
    73  func NewStmtList(stmt interface{}) ([]ast.Stmt, error) {
    74  	s, ok := stmt.(ast.Stmt)
    75  	if !ok {
    76  		return nil, fmt.Errorf("invalid statement type; expected ast.Stmt, got %T", stmt)
    77  	}
    78  	return []ast.Stmt{s}, nil
    79  }
    80  
    81  // AppendStmt appends stmt to the given statement list.
    82  func AppendStmt(list, stmt interface{}) ([]ast.Stmt, error) {
    83  	l, ok := list.([]ast.Stmt)
    84  	if !ok {
    85  		return nil, fmt.Errorf("invalid statement list type; expected []ast.Stmt, got %T", list)
    86  	}
    87  	s, ok := stmt.(ast.Stmt)
    88  	if !ok {
    89  		return nil, fmt.Errorf("invalid statement type; expected ast.Stmt, got %T", stmt)
    90  	}
    91  	return append(l, s), nil
    92  }
    93  
    94  // --- [ Node statement ] ------------------------------------------------------
    95  
    96  // NewNodeStmt returns a new node statement based on the given node and optional
    97  // attributes.
    98  func NewNodeStmt(node, optAttrs interface{}) (*ast.NodeStmt, error) {
    99  	n, ok := node.(*ast.Node)
   100  	if !ok {
   101  		return nil, fmt.Errorf("invalid node type; expected *ast.Node, got %T", node)
   102  	}
   103  	attrs, ok := optAttrs.([]*ast.Attr)
   104  	if optAttrs != nil && !ok {
   105  		return nil, fmt.Errorf("invalid attributes type; expected []*ast.Attr or nil, got %T", optAttrs)
   106  	}
   107  	return &ast.NodeStmt{Node: n, Attrs: attrs}, nil
   108  }
   109  
   110  // --- [ Edge statement ] ------------------------------------------------------
   111  
   112  // NewEdgeStmt returns a new edge statement based on the given source vertex,
   113  // outgoing edge and optional attributes.
   114  func NewEdgeStmt(from, to, optAttrs interface{}) (*ast.EdgeStmt, error) {
   115  	f, ok := from.(ast.Vertex)
   116  	if !ok {
   117  		return nil, fmt.Errorf("invalid source vertex type; expected ast.Vertex, got %T", from)
   118  	}
   119  	t, ok := to.(*ast.Edge)
   120  	if !ok {
   121  		return nil, fmt.Errorf("invalid outgoing edge type; expected *ast.Edge, got %T", to)
   122  	}
   123  	attrs, ok := optAttrs.([]*ast.Attr)
   124  	if optAttrs != nil && !ok {
   125  		return nil, fmt.Errorf("invalid attributes type; expected []*ast.Attr or nil, got %T", optAttrs)
   126  	}
   127  	return &ast.EdgeStmt{From: f, To: t, Attrs: attrs}, nil
   128  }
   129  
   130  // NewEdge returns a new edge based on the given edge direction, destination
   131  // vertex and optional outgoing edge.
   132  func NewEdge(directed, vertex, optTo interface{}) (*ast.Edge, error) {
   133  	d, ok := directed.(bool)
   134  	if !ok {
   135  		return nil, fmt.Errorf("invalid direction type; expected bool, got %T", directed)
   136  	}
   137  	v, ok := vertex.(ast.Vertex)
   138  	if !ok {
   139  		return nil, fmt.Errorf("invalid destination vertex type; expected ast.Vertex, got %T", vertex)
   140  	}
   141  	to, ok := optTo.(*ast.Edge)
   142  	if optTo != nil && !ok {
   143  		return nil, fmt.Errorf("invalid outgoing edge type; expected *ast.Edge or nil, got %T", optTo)
   144  	}
   145  	return &ast.Edge{Directed: d, Vertex: v, To: to}, nil
   146  }
   147  
   148  // --- [ Attribute statement ] -------------------------------------------------
   149  
   150  // NewAttrStmt returns a new attribute statement based on the given graph
   151  // component kind and attributes.
   152  func NewAttrStmt(kind, optAttrs interface{}) (*ast.AttrStmt, error) {
   153  	k, ok := kind.(ast.Kind)
   154  	if !ok {
   155  		return nil, fmt.Errorf("invalid graph component kind type; expected ast.Kind, got %T", kind)
   156  	}
   157  	attrs, ok := optAttrs.([]*ast.Attr)
   158  	if optAttrs != nil && !ok {
   159  		return nil, fmt.Errorf("invalid attributes type; expected []*ast.Attr or nil, got %T", optAttrs)
   160  	}
   161  	return &ast.AttrStmt{Kind: k, Attrs: attrs}, nil
   162  }
   163  
   164  // NewAttrList returns a new attribute list based on the given attribute.
   165  func NewAttrList(attr interface{}) ([]*ast.Attr, error) {
   166  	a, ok := attr.(*ast.Attr)
   167  	if !ok {
   168  		return nil, fmt.Errorf("invalid attribute type; expected *ast.Attr, got %T", attr)
   169  	}
   170  	return []*ast.Attr{a}, nil
   171  }
   172  
   173  // AppendAttr appends attr to the given attribute list.
   174  func AppendAttr(list, attr interface{}) ([]*ast.Attr, error) {
   175  	l, ok := list.([]*ast.Attr)
   176  	if !ok {
   177  		return nil, fmt.Errorf("invalid attribute list type; expected []*ast.Attr, got %T", list)
   178  	}
   179  	a, ok := attr.(*ast.Attr)
   180  	if !ok {
   181  		return nil, fmt.Errorf("invalid attribute type; expected *ast.Attr, got %T", attr)
   182  	}
   183  	return append(l, a), nil
   184  }
   185  
   186  // AppendAttrList appends the optional attrs to the given optional attribute
   187  // list.
   188  func AppendAttrList(optList, optAttrs interface{}) ([]*ast.Attr, error) {
   189  	list, ok := optList.([]*ast.Attr)
   190  	if optList != nil && !ok {
   191  		return nil, fmt.Errorf("invalid attribute list type; expected []*ast.Attr or nil, got %T", optList)
   192  	}
   193  	attrs, ok := optAttrs.([]*ast.Attr)
   194  	if optAttrs != nil && !ok {
   195  		return nil, fmt.Errorf("invalid attributes type; expected []*ast.Attr or nil, got %T", optAttrs)
   196  	}
   197  	return append(list, attrs...), nil
   198  }
   199  
   200  // --- [ Attribute ] -----------------------------------------------------------
   201  
   202  // NewAttr returns a new attribute based on the given key-value pair.
   203  func NewAttr(key, val interface{}) (*ast.Attr, error) {
   204  	k, ok := key.(string)
   205  	if !ok {
   206  		return nil, fmt.Errorf("invalid key type; expected string, got %T", key)
   207  	}
   208  	v, ok := val.(string)
   209  	if !ok {
   210  		return nil, fmt.Errorf("invalid value type; expected string, got %T", val)
   211  	}
   212  	return &ast.Attr{Key: k, Val: v}, nil
   213  }
   214  
   215  // --- [ Subgraph ] ------------------------------------------------------------
   216  
   217  // NewSubgraph returns a new subgraph based on the given optional subgraph ID
   218  // and optional statements.
   219  func NewSubgraph(optID, optStmts interface{}) (*ast.Subgraph, error) {
   220  	id, ok := optID.(string)
   221  	if optID != nil && !ok {
   222  		return nil, fmt.Errorf("invalid ID type; expected string or nil, got %T", optID)
   223  	}
   224  	stmts, ok := optStmts.([]ast.Stmt)
   225  	if optStmts != nil && !ok {
   226  		return nil, fmt.Errorf("invalid statements type; expected []ast.Stmt or nil, got %T", optStmts)
   227  	}
   228  	return &ast.Subgraph{ID: id, Stmts: stmts}, nil
   229  }
   230  
   231  // === [ Vertices ] ============================================================
   232  
   233  // --- [ Node identifier ] -----------------------------------------------------
   234  
   235  // NewNode returns a new node based on the given node id and optional port.
   236  func NewNode(id, optPort interface{}) (*ast.Node, error) {
   237  	i, ok := id.(string)
   238  	if !ok {
   239  		return nil, fmt.Errorf("invalid ID type; expected string, got %T", id)
   240  	}
   241  	port, ok := optPort.(*ast.Port)
   242  	if optPort != nil && !ok {
   243  		return nil, fmt.Errorf("invalid port type; expected *ast.Port or nil, got %T", optPort)
   244  	}
   245  	return &ast.Node{ID: i, Port: port}, nil
   246  }
   247  
   248  // NewPort returns a new port based on the given id and optional compass point.
   249  func NewPort(id, optCompassPoint interface{}) (*ast.Port, error) {
   250  	// Note, if optCompassPoint is nil, id may be either an identifier or a
   251  	// compass point.
   252  	//
   253  	// The following strings are valid compass points:
   254  	//
   255  	//    "n", "ne", "e", "se", "s", "sw", "w", "nw", "c" and "_"
   256  	i, ok := id.(string)
   257  	if !ok {
   258  		return nil, fmt.Errorf("invalid ID type; expected string, got %T", id)
   259  	}
   260  
   261  	// Early return if optional compass point is absent and ID is a valid compass
   262  	// point.
   263  	if optCompassPoint == nil {
   264  		if compassPoint, ok := getCompassPoint(i); ok {
   265  			return &ast.Port{CompassPoint: compassPoint}, nil
   266  		}
   267  	}
   268  
   269  	c, ok := optCompassPoint.(string)
   270  	if optCompassPoint != nil && !ok {
   271  		return nil, fmt.Errorf("invalid compass point type; expected string or nil, got %T", optCompassPoint)
   272  	}
   273  	compassPoint, _ := getCompassPoint(c)
   274  	return &ast.Port{ID: i, CompassPoint: compassPoint}, nil
   275  }
   276  
   277  // getCompassPoint returns the corresponding compass point to the given string,
   278  // and a boolean value indicating if such a compass point exists.
   279  func getCompassPoint(s string) (ast.CompassPoint, bool) {
   280  	switch s {
   281  	case "_":
   282  		return ast.CompassPointDefault, true
   283  	case "n":
   284  		return ast.CompassPointNorth, true
   285  	case "ne":
   286  		return ast.CompassPointNorthEast, true
   287  	case "e":
   288  		return ast.CompassPointEast, true
   289  	case "se":
   290  		return ast.CompassPointSouthEast, true
   291  	case "s":
   292  		return ast.CompassPointSouth, true
   293  	case "sw":
   294  		return ast.CompassPointSouthWest, true
   295  	case "w":
   296  		return ast.CompassPointWest, true
   297  	case "nw":
   298  		return ast.CompassPointNorthWest, true
   299  	case "c":
   300  		return ast.CompassPointCenter, true
   301  	}
   302  	return ast.CompassPointNone, false
   303  }
   304  
   305  // === [ Identifiers ] =========================================================
   306  
   307  // NewID returns a new identifier based on the given ID token.
   308  func NewID(id interface{}) (string, error) {
   309  	i, ok := id.(*token.Token)
   310  	if !ok {
   311  		return "", fmt.Errorf("invalid identifier type; expected *token.Token, got %T", id)
   312  	}
   313  	s := string(i.Lit)
   314  
   315  	// As another aid for readability, dot allows double-quoted strings to span
   316  	// multiple physical lines using the standard C convention of a backslash
   317  	// immediately preceding a newline character.
   318  	if strings.HasPrefix(s, `"`) && strings.HasSuffix(s, `"`) {
   319  		// Strip "\\\n" sequences.
   320  		s = strings.Replace(s, "\\\n", "", -1)
   321  	}
   322  
   323  	// TODO: Add support for concatenated using a '+' operator.
   324  
   325  	return s, nil
   326  }