Files
gonum/graph/formats/dot/internal/astx/astx.go
2017-08-16 13:54:16 +02:00

329 lines
11 KiB
Go

// This file is dual licensed under CC0 and The gonum license.
//
// 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.
//
// Copyright ©2017 Robin Eklind.
// This file is made available under a Creative Commons CC0 1.0
// Universal Public Domain Dedication.
// Package astx implements utility functions for generating abstract syntax
// trees of Graphviz DOT graphs.
package astx // import "gonum.org/v1/gonum/graph/formats/dot/internal/astx"
import (
"fmt"
"strings"
"gonum.org/v1/gonum/graph/formats/dot/ast"
"gonum.org/v1/gonum/graph/formats/dot/internal/token"
)
// === [ File ] ================================================================
// NewFile returns a new file based on the given graph.
func NewFile(graph interface{}) (*ast.File, error) {
g, ok := graph.(*ast.Graph)
if !ok {
return nil, fmt.Errorf("invalid graph type; expected *ast.Graph, got %T", graph)
}
return &ast.File{Graphs: []*ast.Graph{g}}, nil
}
// AppendGraph appends graph to the given file.
func AppendGraph(file, graph interface{}) (*ast.File, error) {
f, ok := file.(*ast.File)
if !ok {
return nil, fmt.Errorf("invalid file type; expected *ast.File, got %T", file)
}
g, ok := graph.(*ast.Graph)
if !ok {
return nil, fmt.Errorf("invalid graph type; expected *ast.Graph, got %T", graph)
}
f.Graphs = append(f.Graphs, g)
return f, nil
}
// === [ Graphs ] ==============================================================
// NewGraph returns a new graph based on the given graph strictness, direction,
// optional ID and optional statements.
func NewGraph(strict, directed, optID, optStmts interface{}) (*ast.Graph, error) {
s, ok := strict.(bool)
if !ok {
return nil, fmt.Errorf("invalid strictness type; expected bool, got %T", strict)
}
d, ok := directed.(bool)
if !ok {
return nil, fmt.Errorf("invalid direction type; expected bool, got %T", directed)
}
id, ok := optID.(string)
if optID != nil && !ok {
return nil, fmt.Errorf("invalid ID type; expected string or nil, got %T", optID)
}
stmts, ok := optStmts.([]ast.Stmt)
if optStmts != nil && !ok {
return nil, fmt.Errorf("invalid statements type; expected []ast.Stmt or nil, got %T", optStmts)
}
return &ast.Graph{Strict: s, Directed: d, ID: id, Stmts: stmts}, nil
}
// === [ Statements ] ==========================================================
// NewStmtList returns a new statement list based on the given statement.
func NewStmtList(stmt interface{}) ([]ast.Stmt, error) {
s, ok := stmt.(ast.Stmt)
if !ok {
return nil, fmt.Errorf("invalid statement type; expected ast.Stmt, got %T", stmt)
}
return []ast.Stmt{s}, nil
}
// AppendStmt appends stmt to the given statement list.
func AppendStmt(list, stmt interface{}) ([]ast.Stmt, error) {
l, ok := list.([]ast.Stmt)
if !ok {
return nil, fmt.Errorf("invalid statement list type; expected []ast.Stmt, got %T", list)
}
s, ok := stmt.(ast.Stmt)
if !ok {
return nil, fmt.Errorf("invalid statement type; expected ast.Stmt, got %T", stmt)
}
return append(l, s), nil
}
// --- [ Node statement ] ------------------------------------------------------
// NewNodeStmt returns a new node statement based on the given node and optional
// attributes.
func NewNodeStmt(node, optAttrs interface{}) (*ast.NodeStmt, error) {
n, ok := node.(*ast.Node)
if !ok {
return nil, fmt.Errorf("invalid node type; expected *ast.Node, got %T", node)
}
attrs, ok := optAttrs.([]*ast.Attr)
if optAttrs != nil && !ok {
return nil, fmt.Errorf("invalid attributes type; expected []*ast.Attr or nil, got %T", optAttrs)
}
return &ast.NodeStmt{Node: n, Attrs: attrs}, nil
}
// --- [ Edge statement ] ------------------------------------------------------
// NewEdgeStmt returns a new edge statement based on the given source vertex,
// outgoing edge and optional attributes.
func NewEdgeStmt(from, to, optAttrs interface{}) (*ast.EdgeStmt, error) {
f, ok := from.(ast.Vertex)
if !ok {
return nil, fmt.Errorf("invalid source vertex type; expected ast.Vertex, got %T", from)
}
t, ok := to.(*ast.Edge)
if !ok {
return nil, fmt.Errorf("invalid outgoing edge type; expected *ast.Edge, got %T", to)
}
attrs, ok := optAttrs.([]*ast.Attr)
if optAttrs != nil && !ok {
return nil, fmt.Errorf("invalid attributes type; expected []*ast.Attr or nil, got %T", optAttrs)
}
return &ast.EdgeStmt{From: f, To: t, Attrs: attrs}, nil
}
// NewEdge returns a new edge based on the given edge direction, destination
// vertex and optional outgoing edge.
func NewEdge(directed, vertex, optTo interface{}) (*ast.Edge, error) {
d, ok := directed.(bool)
if !ok {
return nil, fmt.Errorf("invalid direction type; expected bool, got %T", directed)
}
v, ok := vertex.(ast.Vertex)
if !ok {
return nil, fmt.Errorf("invalid destination vertex type; expected ast.Vertex, got %T", vertex)
}
to, ok := optTo.(*ast.Edge)
if optTo != nil && !ok {
return nil, fmt.Errorf("invalid outgoing edge type; expected *ast.Edge or nil, got %T", optTo)
}
return &ast.Edge{Directed: d, Vertex: v, To: to}, nil
}
// --- [ Attribute statement ] -------------------------------------------------
// NewAttrStmt returns a new attribute statement based on the given graph
// component kind and attributes.
func NewAttrStmt(kind, optAttrs interface{}) (*ast.AttrStmt, error) {
k, ok := kind.(ast.Kind)
if !ok {
return nil, fmt.Errorf("invalid graph component kind type; expected ast.Kind, got %T", kind)
}
attrs, ok := optAttrs.([]*ast.Attr)
if optAttrs != nil && !ok {
return nil, fmt.Errorf("invalid attributes type; expected []*ast.Attr or nil, got %T", optAttrs)
}
return &ast.AttrStmt{Kind: k, Attrs: attrs}, nil
}
// NewAttrList returns a new attribute list based on the given attribute.
func NewAttrList(attr interface{}) ([]*ast.Attr, error) {
a, ok := attr.(*ast.Attr)
if !ok {
return nil, fmt.Errorf("invalid attribute type; expected *ast.Attr, got %T", attr)
}
return []*ast.Attr{a}, nil
}
// AppendAttr appends attr to the given attribute list.
func AppendAttr(list, attr interface{}) ([]*ast.Attr, error) {
l, ok := list.([]*ast.Attr)
if !ok {
return nil, fmt.Errorf("invalid attribute list type; expected []*ast.Attr, got %T", list)
}
a, ok := attr.(*ast.Attr)
if !ok {
return nil, fmt.Errorf("invalid attribute type; expected *ast.Attr, got %T", attr)
}
return append(l, a), nil
}
// AppendAttrList appends the optional attrs to the given optional attribute
// list.
func AppendAttrList(optList, optAttrs interface{}) ([]*ast.Attr, error) {
list, ok := optList.([]*ast.Attr)
if optList != nil && !ok {
return nil, fmt.Errorf("invalid attribute list type; expected []*ast.Attr or nil, got %T", optList)
}
attrs, ok := optAttrs.([]*ast.Attr)
if optAttrs != nil && !ok {
return nil, fmt.Errorf("invalid attributes type; expected []*ast.Attr or nil, got %T", optAttrs)
}
return append(list, attrs...), nil
}
// --- [ Attribute ] -----------------------------------------------------------
// NewAttr returns a new attribute based on the given key-value pair.
func NewAttr(key, val interface{}) (*ast.Attr, error) {
k, ok := key.(string)
if !ok {
return nil, fmt.Errorf("invalid key type; expected string, got %T", key)
}
v, ok := val.(string)
if !ok {
return nil, fmt.Errorf("invalid value type; expected string, got %T", val)
}
return &ast.Attr{Key: k, Val: v}, nil
}
// --- [ Subgraph ] ------------------------------------------------------------
// NewSubgraph returns a new subgraph based on the given optional subgraph ID
// and optional statements.
func NewSubgraph(optID, optStmts interface{}) (*ast.Subgraph, error) {
id, ok := optID.(string)
if optID != nil && !ok {
return nil, fmt.Errorf("invalid ID type; expected string or nil, got %T", optID)
}
stmts, ok := optStmts.([]ast.Stmt)
if optStmts != nil && !ok {
return nil, fmt.Errorf("invalid statements type; expected []ast.Stmt or nil, got %T", optStmts)
}
return &ast.Subgraph{ID: id, Stmts: stmts}, nil
}
// === [ Vertices ] ============================================================
// --- [ Node identifier ] -----------------------------------------------------
// NewNode returns a new node based on the given node id and optional port.
func NewNode(id, optPort interface{}) (*ast.Node, error) {
i, ok := id.(string)
if !ok {
return nil, fmt.Errorf("invalid ID type; expected string, got %T", id)
}
port, ok := optPort.(*ast.Port)
if optPort != nil && !ok {
return nil, fmt.Errorf("invalid port type; expected *ast.Port or nil, got %T", optPort)
}
return &ast.Node{ID: i, Port: port}, nil
}
// NewPort returns a new port based on the given id and optional compass point.
func NewPort(id, optCompassPoint interface{}) (*ast.Port, error) {
// Note, if optCompassPoint is nil, id may be either an identifier or a
// compass point.
//
// The following strings are valid compass points:
//
// "n", "ne", "e", "se", "s", "sw", "w", "nw", "c" and "_"
i, ok := id.(string)
if !ok {
return nil, fmt.Errorf("invalid ID type; expected string, got %T", id)
}
// Early return if optional compass point is absent and ID is a valid compass
// point.
if optCompassPoint == nil {
if compassPoint, ok := getCompassPoint(i); ok {
return &ast.Port{CompassPoint: compassPoint}, nil
}
}
c, ok := optCompassPoint.(string)
if optCompassPoint != nil && !ok {
return nil, fmt.Errorf("invalid compass point type; expected string or nil, got %T", optCompassPoint)
}
compassPoint, _ := getCompassPoint(c)
return &ast.Port{ID: i, CompassPoint: compassPoint}, nil
}
// getCompassPoint returns the corresponding compass point to the given string,
// and a boolean value indicating if such a compass point exists.
func getCompassPoint(s string) (ast.CompassPoint, bool) {
switch s {
case "_":
return ast.CompassPointDefault, true
case "n":
return ast.CompassPointNorth, true
case "ne":
return ast.CompassPointNorthEast, true
case "e":
return ast.CompassPointEast, true
case "se":
return ast.CompassPointSouthEast, true
case "s":
return ast.CompassPointSouth, true
case "sw":
return ast.CompassPointSouthWest, true
case "w":
return ast.CompassPointWest, true
case "nw":
return ast.CompassPointNorthWest, true
case "c":
return ast.CompassPointCenter, true
}
return ast.CompassPointDefault, false
}
// === [ Identifiers ] =========================================================
// NewID returns a new identifier based on the given ID token.
func NewID(id interface{}) (string, error) {
i, ok := id.(*token.Token)
if !ok {
return "", fmt.Errorf("invalid identifier type; expected *token.Token, got %T", id)
}
s := string(i.Lit)
// As another aid for readability, dot allows double-quoted strings to span
// multiple physical lines using the standard C convention of a backslash
// immediately preceding a newline character.
if strings.HasPrefix(s, `"`) && strings.HasSuffix(s, `"`) {
// Strip "\\\n" sequences.
s = strings.Replace(s, "\\\n", "", -1)
}
// TODO: Add support for concatenated using a '+' operator.
return s, nil
}