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 }