diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 2f0ee6e2ee3f..d5498c23a803 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -20,6 +20,10 @@ "Comment": "weekly-50", "Rev": "7fc9d958c83464bd7650240569bf93a102266e6a" }, + { + "ImportPath": "code.google.com/p/gographviz", + "Rev": "454bc64fdfa20468f637d99162e1edc60c1a6677" + }, { "ImportPath": "code.google.com/p/google-api-go-client/compute/v1", "Comment": "release-105", diff --git a/Godeps/_workspace/src/code.google.com/p/gographviz/analyse.go b/Godeps/_workspace/src/code.google.com/p/gographviz/analyse.go new file mode 100644 index 000000000000..cdcb21fc6acd --- /dev/null +++ b/Godeps/_workspace/src/code.google.com/p/gographviz/analyse.go @@ -0,0 +1,168 @@ +//Copyright 2013 Vastech SA (PTY) LTD +// +//Licensed under the Apache License, Version 2.0 (the "License"); +//you may not use this file except in compliance with the License. +//You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, +//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//See the License for the specific language governing permissions and +//limitations under the License. + +package gographviz + +import ( + "code.google.com/p/gographviz/ast" +) + +//Creates a Graph structure by analysing an Abstract Syntax Tree representing a parsed graph. +func NewAnalysedGraph(graph *ast.Graph) Interface { + g := NewGraph() + Analyse(graph, g) + return g +} + +//Analyses an Abstract Syntax Tree representing a parsed graph into a newly created graph structure Interface. +func Analyse(graph *ast.Graph, g Interface) { + graph.Walk(&graphVisitor{g}) +} + +type nilVisitor struct { +} + +func (this *nilVisitor) Visit(v ast.Elem) ast.Visitor { + return this +} + +type graphVisitor struct { + g Interface +} + +func (this *graphVisitor) Visit(v ast.Elem) ast.Visitor { + graph, ok := v.(*ast.Graph) + if !ok { + return this + } + this.g.SetStrict(graph.Strict) + this.g.SetDir(graph.Type == ast.DIGRAPH) + graphName := graph.Id.String() + this.g.SetName(graphName) + return newStmtVisitor(this.g, graphName) +} + +func newStmtVisitor(g Interface, graphName string) *stmtVisitor { + return &stmtVisitor{g, graphName, make(Attrs), make(Attrs), make(Attrs)} +} + +type stmtVisitor struct { + g Interface + graphName string + currentNodeAttrs Attrs + currentEdgeAttrs Attrs + currentGraphAttrs Attrs +} + +func (this *stmtVisitor) Visit(v ast.Elem) ast.Visitor { + switch s := v.(type) { + case ast.NodeStmt: + return this.nodeStmt(s) + case ast.EdgeStmt: + return this.edgeStmt(s) + case ast.NodeAttrs: + return this.nodeAttrs(s) + case ast.EdgeAttrs: + return this.edgeAttrs(s) + case ast.GraphAttrs: + return this.graphAttrs(s) + case *ast.SubGraph: + return this.subGraph(s) + case *ast.Attr: + return this.attr(s) + case ast.AttrList: + return &nilVisitor{} + default: + //fmt.Fprintf(os.Stderr, "unknown stmt %T\n", v) + } + return this +} + +func ammend(attrs Attrs, add Attrs) Attrs { + for key, value := range add { + if _, ok := attrs[key]; !ok { + attrs[key] = value + } + } + return attrs +} + +func overwrite(attrs Attrs, overwrite Attrs) Attrs { + for key, value := range overwrite { + attrs[key] = value + } + return attrs +} + +func (this *stmtVisitor) nodeStmt(stmt ast.NodeStmt) ast.Visitor { + attrs := Attrs(stmt.Attrs.GetMap()) + attrs = ammend(attrs, this.currentNodeAttrs) + this.g.AddNode(this.graphName, stmt.NodeId.String(), attrs) + return &nilVisitor{} +} + +func (this *stmtVisitor) edgeStmt(stmt ast.EdgeStmt) ast.Visitor { + attrs := stmt.Attrs.GetMap() + attrs = ammend(attrs, this.currentEdgeAttrs) + src := stmt.Source.GetId() + srcName := src.String() + if stmt.Source.IsNode() { + this.g.AddNode(this.graphName, srcName, this.currentNodeAttrs.Copy()) + } + srcPort := stmt.Source.GetPort() + for i := range stmt.EdgeRHS { + directed := bool(stmt.EdgeRHS[i].Op) + dst := stmt.EdgeRHS[i].Destination.GetId() + dstName := dst.String() + if stmt.EdgeRHS[i].Destination.IsNode() { + this.g.AddNode(this.graphName, dstName, this.currentNodeAttrs.Copy()) + } + dstPort := stmt.EdgeRHS[i].Destination.GetPort() + this.g.AddPortEdge(srcName, srcPort.String(), dstName, dstPort.String(), directed, attrs) + src = dst + srcPort = dstPort + srcName = dstName + } + return this +} + +func (this *stmtVisitor) nodeAttrs(stmt ast.NodeAttrs) ast.Visitor { + this.currentNodeAttrs = overwrite(this.currentNodeAttrs, ast.AttrList(stmt).GetMap()) + return &nilVisitor{} +} + +func (this *stmtVisitor) edgeAttrs(stmt ast.EdgeAttrs) ast.Visitor { + this.currentEdgeAttrs = overwrite(this.currentEdgeAttrs, ast.AttrList(stmt).GetMap()) + return &nilVisitor{} +} + +func (this *stmtVisitor) graphAttrs(stmt ast.GraphAttrs) ast.Visitor { + attrs := ast.AttrList(stmt).GetMap() + for key, value := range attrs { + this.g.AddAttr(this.graphName, key, value) + } + this.currentGraphAttrs = overwrite(this.currentGraphAttrs, attrs) + return &nilVisitor{} +} + +func (this *stmtVisitor) subGraph(stmt *ast.SubGraph) ast.Visitor { + subGraphName := stmt.Id.String() + this.g.AddSubGraph(this.graphName, subGraphName, this.currentGraphAttrs) + return newStmtVisitor(this.g, subGraphName) +} + +func (this *stmtVisitor) attr(stmt *ast.Attr) ast.Visitor { + this.g.AddAttr(this.graphName, stmt.Field.String(), stmt.Value.String()) + return this +} diff --git a/Godeps/_workspace/src/code.google.com/p/gographviz/ast/ast.go b/Godeps/_workspace/src/code.google.com/p/gographviz/ast/ast.go new file mode 100644 index 000000000000..a5b8a6511c1d --- /dev/null +++ b/Godeps/_workspace/src/code.google.com/p/gographviz/ast/ast.go @@ -0,0 +1,690 @@ +//Copyright 2013 Vastech SA (PTY) LTD +// +//Licensed under the Apache License, Version 2.0 (the "License"); +//you may not use this file except in compliance with the License. +//You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, +//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//See the License for the specific language governing permissions and +//limitations under the License. + +//Abstract Syntax Tree representing the DOT grammar +package ast + +import ( + "code.google.com/p/gographviz/token" + "errors" + "fmt" + "math/rand" + "sort" + "strings" +) + +var ( + r = rand.New(rand.NewSource(1234)) +) + +type Visitor interface { + Visit(e Elem) Visitor +} + +type Elem interface { + String() string +} + +type Walkable interface { + Walk(v Visitor) +} + +type Bool bool + +const ( + FALSE = Bool(false) + TRUE = Bool(true) +) + +func (this Bool) String() string { + switch this { + case false: + return "false" + case true: + return "true" + } + panic("unreachable") +} + +func (this Bool) Walk(v Visitor) { + if v == nil { + return + } + v.Visit(this) +} + +type GraphType bool + +const ( + GRAPH = GraphType(false) + DIGRAPH = GraphType(true) +) + +func (this GraphType) String() string { + switch this { + case false: + return "graph" + case true: + return "digraph" + } + panic("unreachable") +} + +func (this GraphType) Walk(v Visitor) { + if v == nil { + return + } + v.Visit(this) +} + +type Graph struct { + Type GraphType + Strict bool + Id Id + StmtList StmtList +} + +func NewGraph(t, strict, id, l Elem) (*Graph, error) { + g := &Graph{Type: t.(GraphType), Strict: bool(strict.(Bool)), Id: Id("")} + if id != nil { + g.Id = id.(Id) + } + if l != nil { + g.StmtList = l.(StmtList) + } + return g, nil +} + +func (this *Graph) String() string { + s := this.Type.String() + " " + this.Id.String() + " {\n" + if this.StmtList != nil { + s += this.StmtList.String() + } + s += "\n}\n" + return s +} + +func (this *Graph) Walk(v Visitor) { + if v == nil { + return + } + v = v.Visit(this) + this.Type.Walk(v) + this.Id.Walk(v) + this.StmtList.Walk(v) +} + +type StmtList []Stmt + +func NewStmtList(s Elem) (StmtList, error) { + ss := make(StmtList, 1) + ss[0] = s.(Stmt) + return ss, nil +} + +func AppendStmtList(ss, s Elem) (StmtList, error) { + this := ss.(StmtList) + this = append(this, s.(Stmt)) + return this, nil +} + +func (this StmtList) String() string { + if len(this) == 0 { + return "" + } + s := "" + for i := 0; i < len(this); i++ { + ss := this[i].String() + if len(ss) > 0 { + s += "\t" + ss + ";\n" + } + } + return s +} + +func (this StmtList) Walk(v Visitor) { + if v == nil { + return + } + v = v.Visit(this) + for i := range this { + this[i].Walk(v) + } +} + +type Stmt interface { + Elem + Walkable + isStmt() +} + +func (this NodeStmt) isStmt() {} +func (this EdgeStmt) isStmt() {} +func (this EdgeAttrs) isStmt() {} +func (this NodeAttrs) isStmt() {} +func (this GraphAttrs) isStmt() {} +func (this *SubGraph) isStmt() {} +func (this *Attr) isStmt() {} + +type SubGraph struct { + Id Id + StmtList StmtList +} + +func NewSubGraph(id, l Elem) (*SubGraph, error) { + g := &SubGraph{Id: Id(fmt.Sprintf("anon%d", r.Int63()))} + if id != nil { + if len(id.(Id)) > 0 { + g.Id = id.(Id) + } + } + if l != nil { + g.StmtList = l.(StmtList) + } + return g, nil +} + +func (this *SubGraph) GetId() Id { + return this.Id +} + +func (this *SubGraph) GetPort() Port { + port, err := NewPort(nil, nil) + if err != nil { + panic(err) + } + return port +} + +func (this *SubGraph) String() string { + gName := this.Id.String() + if strings.HasPrefix(gName, "anon") { + gName = "" + } + s := "subgraph " + this.Id.String() + " {\n" + if this.StmtList != nil { + s += this.StmtList.String() + } + s += "\n}\n" + return s +} + +func (this *SubGraph) Walk(v Visitor) { + if v == nil { + return + } + v = v.Visit(this) + this.Id.Walk(v) + this.StmtList.Walk(v) +} + +type EdgeAttrs AttrList + +func NewEdgeAttrs(a Elem) (EdgeAttrs, error) { + return EdgeAttrs(a.(AttrList)), nil +} + +func (this EdgeAttrs) String() string { + s := AttrList(this).String() + if len(s) == 0 { + return "" + } + return `edge ` + s +} + +func (this EdgeAttrs) Walk(v Visitor) { + if v == nil { + return + } + v = v.Visit(this) + for i := range this { + this[i].Walk(v) + } +} + +type NodeAttrs AttrList + +func NewNodeAttrs(a Elem) (NodeAttrs, error) { + return NodeAttrs(a.(AttrList)), nil +} + +func (this NodeAttrs) String() string { + s := AttrList(this).String() + if len(s) == 0 { + return "" + } + return `node ` + s +} + +func (this NodeAttrs) Walk(v Visitor) { + if v == nil { + return + } + v = v.Visit(this) + for i := range this { + this[i].Walk(v) + } +} + +type GraphAttrs AttrList + +func NewGraphAttrs(a Elem) (GraphAttrs, error) { + return GraphAttrs(a.(AttrList)), nil +} + +func (this GraphAttrs) String() string { + s := AttrList(this).String() + if len(s) == 0 { + return "" + } + return `graph ` + s +} + +func (this GraphAttrs) Walk(v Visitor) { + if v == nil { + return + } + v = v.Visit(this) + for i := range this { + this[i].Walk(v) + } +} + +type AttrList []AList + +func NewAttrList(a Elem) (AttrList, error) { + as := make(AttrList, 0) + if a != nil { + as = append(as, a.(AList)) + } + return as, nil +} + +func AppendAttrList(as, a Elem) (AttrList, error) { + this := as.(AttrList) + if a == nil { + return this, nil + } + this = append(this, a.(AList)) + return this, nil +} + +func (this AttrList) String() string { + s := "" + for _, alist := range this { + ss := alist.String() + if len(ss) > 0 { + s += "[ " + ss + " ] " + } + } + if len(s) == 0 { + return "" + } + return s +} + +func (this AttrList) Walk(v Visitor) { + if v == nil { + return + } + v = v.Visit(this) + for i := range this { + this[i].Walk(v) + } +} + +func PutMap(attrmap map[string]string) AttrList { + attrlist := make(AttrList, 1) + attrlist[0] = make(AList, 0) + keys := make([]string, 0, len(attrmap)) + for key := range attrmap { + keys = append(keys, key) + } + sort.Strings(keys) + for _, name := range keys { + value := attrmap[name] + attrlist[0] = append(attrlist[0], &Attr{Id(name), Id(value)}) + } + return attrlist +} + +func (this AttrList) GetMap() map[string]string { + attrs := make(map[string]string) + for _, alist := range this { + for _, attr := range alist { + attrs[attr.Field.String()] = attr.Value.String() + } + } + return attrs +} + +type AList []*Attr + +func NewAList(a Elem) (AList, error) { + as := make(AList, 1) + as[0] = a.(*Attr) + return as, nil +} + +func AppendAList(as, a Elem) (AList, error) { + this := as.(AList) + attr := a.(*Attr) + this = append(this, attr) + return this, nil +} + +func (this AList) String() string { + if len(this) == 0 { + return "" + } + str := this[0].String() + for i := 1; i < len(this); i++ { + str += `, ` + this[i].String() + } + return str +} + +func (this AList) Walk(v Visitor) { + v = v.Visit(this) + for i := range this { + this[i].Walk(v) + } +} + +type Attr struct { + Field Id + Value Id +} + +func NewAttr(f, v Elem) (*Attr, error) { + a := &Attr{Field: f.(Id)} + a.Value = Id("true") + if v != nil { + ok := false + a.Value, ok = v.(Id) + if !ok { + return nil, errors.New(fmt.Sprintf("value = %v", v)) + } + } + return a, nil +} + +func (this *Attr) String() string { + return this.Field.String() + `=` + this.Value.String() +} + +func (this *Attr) Walk(v Visitor) { + if v == nil { + return + } + v = v.Visit(this) + this.Field.Walk(v) + this.Value.Walk(v) +} + +type Location interface { + Elem + Walkable + isLocation() + GetId() Id + GetPort() Port + IsNode() bool +} + +func (this *NodeId) isLocation() {} +func (this *NodeId) IsNode() bool { return true } +func (this *SubGraph) isLocation() {} +func (this *SubGraph) IsNode() bool { return false } + +type EdgeStmt struct { + Source Location + EdgeRHS EdgeRHS + Attrs AttrList +} + +func NewEdgeStmt(id, e, attrs Elem) (*EdgeStmt, error) { + var a AttrList = nil + var err error = nil + if attrs == nil { + a, err = NewAttrList(nil) + if err != nil { + return nil, err + } + } else { + a = attrs.(AttrList) + } + return &EdgeStmt{id.(Location), e.(EdgeRHS), a}, nil +} + +func (this EdgeStmt) String() string { + return strings.TrimSpace(this.Source.String() + this.EdgeRHS.String() + this.Attrs.String()) +} + +func (this EdgeStmt) Walk(v Visitor) { + if v == nil { + return + } + v = v.Visit(this) + this.Source.Walk(v) + this.EdgeRHS.Walk(v) + this.Attrs.Walk(v) +} + +type EdgeRHS []*EdgeRH + +func NewEdgeRHS(op, id Elem) (EdgeRHS, error) { + return EdgeRHS{&EdgeRH{op.(EdgeOp), id.(Location)}}, nil +} + +func AppendEdgeRHS(e, op, id Elem) (EdgeRHS, error) { + erhs := e.(EdgeRHS) + erhs = append(erhs, &EdgeRH{op.(EdgeOp), id.(Location)}) + return erhs, nil +} + +func (this EdgeRHS) String() string { + s := "" + for i := range this { + s += this[i].String() + } + return strings.TrimSpace(s) +} + +func (this EdgeRHS) Walk(v Visitor) { + if v == nil { + return + } + v = v.Visit(this) + for i := range this { + this[i].Walk(v) + } +} + +type EdgeRH struct { + Op EdgeOp + Destination Location +} + +func (this *EdgeRH) String() string { + return strings.TrimSpace(this.Op.String() + this.Destination.String()) +} + +func (this *EdgeRH) Walk(v Visitor) { + if v == nil { + return + } + v = v.Visit(this) + this.Op.Walk(v) + this.Destination.Walk(v) +} + +type NodeStmt struct { + NodeId *NodeId + Attrs AttrList +} + +func NewNodeStmt(id, attrs Elem) (*NodeStmt, error) { + nid := id.(*NodeId) + var a AttrList = nil + var err error = nil + if attrs == nil { + a, err = NewAttrList(nil) + if err != nil { + return nil, err + } + } else { + a = attrs.(AttrList) + } + return &NodeStmt{nid, a}, nil +} + +func (this NodeStmt) String() string { + return strings.TrimSpace(this.NodeId.String() + ` ` + this.Attrs.String()) +} + +func (this NodeStmt) Walk(v Visitor) { + if v == nil { + return + } + v = v.Visit(this) + this.NodeId.Walk(v) + this.Attrs.Walk(v) +} + +type EdgeOp bool + +const ( + DIRECTED EdgeOp = true + UNDIRECTED EdgeOp = false +) + +func (this EdgeOp) String() string { + switch this { + case DIRECTED: + return "->" + case UNDIRECTED: + return "--" + } + panic("unreachable") +} + +func (this EdgeOp) Walk(v Visitor) { + if v == nil { + return + } + v.Visit(this) +} + +type NodeId struct { + Id Id + Port Port +} + +func NewNodeId(id Elem, port Elem) (*NodeId, error) { + if port == nil { + return &NodeId{id.(Id), Port{"", ""}}, nil + } + return &NodeId{id.(Id), port.(Port)}, nil +} + +func MakeNodeId(id string, port string) *NodeId { + p := Port{"", ""} + if len(port) > 0 { + ps := strings.Split(port, ":") + p.Id1 = Id(ps[1]) + if len(ps) > 2 { + p.Id2 = Id(ps[2]) + } + } + return &NodeId{Id(id), p} +} + +func (this *NodeId) String() string { + return this.Id.String() + this.Port.String() +} + +func (this *NodeId) GetId() Id { + return this.Id +} + +func (this *NodeId) GetPort() Port { + return this.Port +} + +func (this *NodeId) Walk(v Visitor) { + if v == nil { + return + } + v = v.Visit(this) + this.Id.Walk(v) + this.Port.Walk(v) +} + +//TODO semantic analysis should decide which Id is an Id and which is a Compass Point +type Port struct { + Id1 Id + Id2 Id +} + +func NewPort(id1, id2 Elem) (Port, error) { + port := Port{Id(""), Id("")} + if id1 != nil { + port.Id1 = id1.(Id) + } + if id2 != nil { + port.Id2 = id2.(Id) + } + return port, nil +} + +func (this Port) String() string { + if len(this.Id1) == 0 { + return "" + } + s := ":" + this.Id1.String() + if len(this.Id2) > 0 { + s += ":" + this.Id2.String() + } + return s +} + +func (this Port) Walk(v Visitor) { + if v == nil { + return + } + v = v.Visit(this) + this.Id1.Walk(v) + this.Id2.Walk(v) +} + +type Id string + +func NewId(id Elem) (Id, error) { + if id == nil { + return Id(""), nil + } + id_lit := string(id.(*token.Token).Lit) + return Id(id_lit), nil +} + +func (this Id) String() string { + return string(this) +} + +func (this Id) Walk(v Visitor) { + if v == nil { + return + } + v.Visit(this) +} diff --git a/Godeps/_workspace/src/code.google.com/p/gographviz/attrs.go b/Godeps/_workspace/src/code.google.com/p/gographviz/attrs.go new file mode 100644 index 000000000000..6d06bb922987 --- /dev/null +++ b/Godeps/_workspace/src/code.google.com/p/gographviz/attrs.go @@ -0,0 +1,71 @@ +//Copyright 2013 Vastech SA (PTY) LTD +// +//Licensed under the Apache License, Version 2.0 (the "License"); +//you may not use this file except in compliance with the License. +//You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, +//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//See the License for the specific language governing permissions and +//limitations under the License. + +package gographviz + +import ( + "fmt" + "os" + "sort" +) + +//Represents attributes for an Edge, Node or Graph. +type Attrs map[string]string + +//Creates an empty Attributes type. +func NewAttrs() Attrs { + return make(Attrs) +} + +//Adds an attribute name and value. +func (this Attrs) Add(field string, value string) { + prev, ok := this[field] + if ok { + fmt.Fprintf(os.Stderr, "WARNING: overwriting field %v value %v, with value %v\n", field, prev, value) + } + this[field] = value +} + +//Adds the attributes into this Attrs type overwriting duplicates. +func (this Attrs) Extend(more Attrs) { + for key, value := range more { + this.Add(key, value) + } +} + +//Only adds the missing attributes to this Attrs type. +func (this Attrs) Ammend(more Attrs) { + for key, value := range more { + if _, ok := this[key]; !ok { + this.Add(key, value) + } + } +} + +func (this Attrs) SortedNames() []string { + keys := make([]string, 0) + for key := range this { + keys = append(keys, key) + } + sort.Strings(keys) + return keys +} + +func (this Attrs) Copy() Attrs { + attrs := make(Attrs) + for k, v := range this { + attrs[k] = v + } + return attrs +} diff --git a/Godeps/_workspace/src/code.google.com/p/gographviz/edges.go b/Godeps/_workspace/src/code.google.com/p/gographviz/edges.go new file mode 100644 index 000000000000..d848c3fe768e --- /dev/null +++ b/Godeps/_workspace/src/code.google.com/p/gographviz/edges.go @@ -0,0 +1,81 @@ +//Copyright 2013 Vastech SA (PTY) LTD +// +//Licensed under the Apache License, Version 2.0 (the "License"); +//you may not use this file except in compliance with the License. +//You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, +//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//See the License for the specific language governing permissions and +//limitations under the License. + +package gographviz + +import ( + "sort" +) + +//Represents an Edge. +type Edge struct { + Src string + SrcPort string + Dst string + DstPort string + Dir bool + Attrs Attrs +} + +//Represents a set of Edges. +type Edges struct { + SrcToDsts map[string]map[string]*Edge + DstToSrcs map[string]map[string]*Edge + Edges []*Edge +} + +//Creates a blank set of Edges. +func NewEdges() *Edges { + return &Edges{make(map[string]map[string]*Edge), make(map[string]map[string]*Edge), make([]*Edge, 0)} +} + +//Adds an Edge to the set of Edges. +func (this *Edges) Add(edge *Edge) { + if _, ok := this.SrcToDsts[edge.Src]; !ok { + this.SrcToDsts[edge.Src] = make(map[string]*Edge) + } + if _, ok := this.SrcToDsts[edge.Src][edge.Dst]; !ok { + this.SrcToDsts[edge.Src][edge.Dst] = edge + } else { + this.SrcToDsts[edge.Src][edge.Dst].Attrs.Extend(edge.Attrs) + } + if _, ok := this.DstToSrcs[edge.Dst]; !ok { + this.DstToSrcs[edge.Dst] = make(map[string]*Edge) + } + if _, ok := this.DstToSrcs[edge.Dst][edge.Src]; !ok { + this.DstToSrcs[edge.Dst][edge.Src] = edge + } + this.Edges = append(this.Edges, edge) +} + +//Retrusn a sorted list of Edges. +func (this Edges) Sorted() []*Edge { + srcs := make([]string, 0, len(this.SrcToDsts)) + for src := range this.SrcToDsts { + srcs = append(srcs, src) + } + sort.Strings(srcs) + edges := make([]*Edge, 0, len(srcs)) + for _, src := range srcs { + dsts := make([]string, 0, len(this.SrcToDsts[src])) + for dst := range this.SrcToDsts[src] { + dsts = append(dsts, dst) + } + sort.Strings(dsts) + for _, dst := range dsts { + edges = append(edges, this.SrcToDsts[src][dst]) + } + } + return edges +} diff --git a/Godeps/_workspace/src/code.google.com/p/gographviz/escape.go b/Godeps/_workspace/src/code.google.com/p/gographviz/escape.go new file mode 100644 index 000000000000..8ce6ee3c57d0 --- /dev/null +++ b/Godeps/_workspace/src/code.google.com/p/gographviz/escape.go @@ -0,0 +1,177 @@ +//Copyright 2013 Vastech SA (PTY) LTD +// +//Licensed under the Apache License, Version 2.0 (the "License"); +//you may not use this file except in compliance with the License. +//You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, +//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//See the License for the specific language governing permissions and +//limitations under the License. + +package gographviz + +import ( + "code.google.com/p/gographviz/scanner" + "code.google.com/p/gographviz/token" + "fmt" + "strings" + "text/template" + "unicode" +) + +type Escape struct { + Interface +} + +//Returns a graph which will try to escape some strings when required +func NewEscape() Interface { + return &Escape{NewGraph()} +} + +func isHtml(s string) bool { + if len(s) == 0 { + return false + } + ss := strings.TrimSpace(s) + if ss[0] != '<' { + return false + } + count := 0 + for _, c := range ss { + if c == '<' { + count += 1 + } + if c == '>' { + count -= 1 + } + } + if count == 0 { + return true + } + return false +} + +func isLetter(ch rune) bool { + return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' || + ch >= 0x80 && unicode.IsLetter(ch) && ch != 'ε' +} + +func isId(s string) bool { + i := 0 + pos := false + for _, c := range s { + if i == 0 { + if !isLetter(c) { + return false + } + pos = true + } + if unicode.IsSpace(c) { + return false + } + if c == '-' { + return false + } + i++ + } + return pos +} + +func isDigit(ch rune) bool { + return '0' <= ch && ch <= '9' || ch >= 0x80 && unicode.IsDigit(ch) +} + +func isNumber(s string) bool { + state := 0 + for _, c := range s { + if state == 0 { + if isDigit(c) || c == '.' { + state = 2 + } else if c == '-' { + state = 1 + } else { + return false + } + } else if state == 1 { + if isDigit(c) || c == '.' { + state = 2 + } + } else if c != '.' && !isDigit(c) { + return false + } + } + return (state == 2) +} + +func isStringLit(s string) bool { + lex := &scanner.Scanner{} + lex.Init([]byte(s), token.DOTTokens) + tok, _ := lex.Scan() + if tok.Type != token.DOTTokens.Type("string_lit") { + return false + } + tok, _ = lex.Scan() + if tok.Type != token.EOF { + return false + } + return true +} + +func esc(s string) string { + if len(s) == 0 { + return s + } + if isHtml(s) { + return s + } + ss := strings.TrimSpace(s) + if ss[0] == '<' { + return fmt.Sprintf("\"%s\"", strings.Replace(s, "\"", "\\\"", -1)) + } + if isId(s) { + return s + } + if isNumber(s) { + return s + } + if isStringLit(s) { + return s + } + return fmt.Sprintf("\"%s\"", template.HTMLEscapeString(s)) +} + +func escAttrs(attrs map[string]string) map[string]string { + newAttrs := make(map[string]string) + for k, v := range attrs { + newAttrs[esc(k)] = esc(v) + } + return newAttrs +} + +func (this *Escape) SetName(name string) { + this.Interface.SetName(esc(name)) +} + +func (this *Escape) AddPortEdge(src, srcPort, dst, dstPort string, directed bool, attrs map[string]string) { + this.Interface.AddPortEdge(esc(src), srcPort, esc(dst), dstPort, directed, escAttrs(attrs)) +} + +func (this *Escape) AddEdge(src, dst string, directed bool, attrs map[string]string) { + this.AddPortEdge(src, "", dst, "", directed, attrs) +} + +func (this *Escape) AddNode(parentGraph string, name string, attrs map[string]string) { + this.Interface.AddNode(esc(parentGraph), esc(name), escAttrs(attrs)) +} + +func (this *Escape) AddAttr(parentGraph string, field, value string) { + this.Interface.AddAttr(esc(parentGraph), esc(field), esc(value)) +} + +func (this *Escape) AddSubGraph(parentGraph string, name string, attrs map[string]string) { + this.Interface.AddSubGraph(esc(parentGraph), esc(name), escAttrs(attrs)) +} diff --git a/Godeps/_workspace/src/code.google.com/p/gographviz/escape_test.go b/Godeps/_workspace/src/code.google.com/p/gographviz/escape_test.go new file mode 100644 index 000000000000..f815f4918754 --- /dev/null +++ b/Godeps/_workspace/src/code.google.com/p/gographviz/escape_test.go @@ -0,0 +1,44 @@ +//Copyright 2013 Vastech SA (PTY) LTD +// +//Licensed under the Apache License, Version 2.0 (the "License"); +//you may not use this file except in compliance with the License. +//You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, +//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//See the License for the specific language governing permissions and +//limitations under the License. + +package gographviz + +import ( + "strings" + "testing" +) + +func TestEscape(t *testing.T) { + g := NewEscape() + g.SetName("asdf adsf") + g.SetDir(true) + g.AddNode("asdf asdf", "kasdf99 99", map[string]string{ + "7; + "a << b"; + "kasdf99 99" [ "= 0x80: + // not ASCII + r, w = utf8.DecodeRune(S.src[S.offset:]) + if r == utf8.RuneError && w == 1 { + S.error(S.pos, "illegal UTF-8 encoding") + } + } + S.offset += w + S.ch = r + } else { + S.pos.Offset = len(S.src) + S.ch = -1 // eof + } +} + +// Init prepares the scanner S to tokenize the text src. Calls to Scan +// will use the error handler err if they encounter a syntax error and +// err is not nil. Also, for each error encountered, the Scanner field +// ErrorCount is incremented by one. The filename parameter is used as +// filename in the token.Position returned by Scan for each token. The +// mode parameter determines how comments and illegal characters are +// handled. +// +func (S *Scanner) Init(src []byte, tokenMap *token.TokenMap) { + // Explicitly initialize all fields since a scanner may be reused. + S.src = src + S.tokenMap = tokenMap + S.pos = token.Position{0, 1, 0} + S.offset = 0 + S.ErrorCount = 0 + S.next() +} + +func charString(ch rune) string { + var s string + switch ch { + case -1: + return "EOF" + case '\a': + s = "\\a" + case '\b': + s = "\\b" + case '\f': + s = "\\f" + case '\n': + s = "\\n" + case '\r': + s = "\\r" + case '\t': + s = "\\t" + case '\v': + s = "\\v" + case '\\': + s = "\\\\" + case '\'': + s = "\\'" + default: + s = string(ch) + } + return "'" + s + "' (U+" + strconv.FormatInt(int64(ch), 16) + ")" +} + +func (S *Scanner) error(pos token.Position, msg string) { + S.ErrorCount++ +} + +func (S *Scanner) expect(ch rune) { + if S.ch != ch { + S.error(S.pos, "expected "+charString(ch)+", found "+charString(S.ch)) + } + S.next() // always make progress +} + +var prefix = []byte("line ") + +func (S *Scanner) scanComment(pos token.Position) { + // first '/' or '#' already consumed + if S.ch == '*' { + /*-style comment */ + S.expect('*') + for S.ch >= 0 { + ch := S.ch + S.next() + if ch == '*' && S.ch == '/' { + S.next() + return + } + } + } else { + //-style comment or #-style comment + for S.ch >= 0 { + S.next() + if S.ch == '\n' { + // '\n' is not part of the comment for purposes of scanning + // (the comment ends on the same line where it started) + if pos.Column == 1 { + text := S.src[pos.Offset+2 : S.pos.Offset] + if bytes.HasPrefix(text, prefix) { + // comment starts at beginning of line with "//line "; + // get filename and line number, if any + i := bytes.Index(text, []byte{':'}) + if i >= 0 { + if line, err := strconv.Atoi(string(text[i+1:])); err == nil && line > 0 { + // valid //line filename:line comment; + // update scanner position + S.pos.Line = line - 1 // -1 since the '\n' has not been consumed yet + } + } + } + } + return + } + } + } + + S.error(pos, "comment not terminated") +} + +func (S *Scanner) findNewline(pos token.Position) bool { + // first '/' already consumed; assume S.ch == '/' || S.ch == '*' + + // read ahead until a newline or non-comment token is found + newline := false + for pos1 := pos; S.ch >= 0; { + if S.ch == '/' { + //-style comment always contains a newline + newline = true + break + } + S.scanComment(pos1) + if pos1.Line < S.pos.Line { + /*-style comment contained a newline */ + newline = true + break + } + S.skipWhitespace() // S.insertSemi is set + if S.ch == '\n' { + newline = true + break + } + if S.ch != '/' { + // non-comment token + break + } + pos1 = S.pos + S.next() + if S.ch != '/' && S.ch != '*' { + // non-comment token + break + } + } + + // reset position to where it was upon calling findNewline + S.pos = pos + S.offset = pos.Offset + 1 + S.next() + + return newline +} + +func isLetter(ch rune) bool { + return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' || + ch >= 0x80 && unicode.IsLetter(ch) && ch != 'ε' +} + +func isDigit(ch rune) bool { + return '0' <= ch && ch <= '9' || ch >= 0x80 && unicode.IsDigit(ch) +} + +func digitVal(ch rune) int { + switch { + case '0' <= ch && ch <= '9': + return int(ch) - '0' + case 'a' <= ch && ch <= 'f': + return int(ch) - 'a' + 10 + case 'A' <= ch && ch <= 'F': + return int(ch) - 'A' + 10 + } + return 16 // larger than any legal digit val +} + +func (S *Scanner) scanEscape(quote rune) { + pos := S.pos + + var i, base, max uint32 + switch S.ch { + case 'a', 'b', 'f', 'n', 'r', 't', 'v', '\\', quote: + S.next() + return + case '0', '1', '2', '3', '4', '5', '6', '7': + i, base, max = 3, 8, 255 + case 'x': + S.next() + i, base, max = 2, 16, 255 + case 'u': + S.next() + i, base, max = 4, 16, unicode.MaxRune + case 'U': + S.next() + i, base, max = 8, 16, unicode.MaxRune + default: + S.next() // always make progress + S.error(pos, "unknown escape sequence") + return + } + + var x uint32 + for ; i > 0; i-- { + d := uint32(digitVal(S.ch)) + if d > base { + S.error(S.pos, "illegal character in escape sequence") + return + } + x = x*base + d + S.next() + } + if x > max || 0xd800 <= x && x < 0xe000 { + S.error(pos, "escape sequence is invalid Unicode code point") + } +} + +func (S *Scanner) scanChar(pos token.Position) { + // '\'' already consumed + + n := 0 + for S.ch != '\'' { + ch := S.ch + n++ + S.next() + if ch == '\n' || ch < 0 { + S.error(pos, "character literal not terminated") + n = 1 + break + } + if ch == '\\' { + S.scanEscape('\'') + } + } + + S.next() + + if n != 1 { + S.error(pos, "illegal character literal") + } +} + +func (S *Scanner) isToken(str string) bool { + return S.tokenMap.Type(str) != token.ILLEGAL +} + +func (S *Scanner) scanIdentifier() token.Type { + pos := S.pos.Offset + for !S.isToken(string(S.ch)) && !unicode.IsSpace(S.ch) && S.ch != '-' { + S.next() + } + if tok := S.tokenMap.Type(string(S.src[pos:S.pos.Offset])); tok != token.ILLEGAL { + return tok + } + return S.tokenMap.Type("id") +} + +func (S *Scanner) scanNumber() token.Type { + for isDigit(S.ch) { + S.next() + } + if S.ch == '.' { + S.next() + for isDigit(S.ch) { + S.next() + } + return S.tokenMap.Type("float_lit") + } + return S.tokenMap.Type("int_lit") +} + +func (S *Scanner) scanHTML() token.Type { + count := 1 + for count > 0 { + if S.ch == '<' { + count += 1 + } + if S.ch == '>' { + count -= 1 + } + S.next() + } + return S.tokenMap.Type("html_lit") +} + +func (S *Scanner) scanSDTLit(pos token.Position) { + // '<' already consumed + S.next() // consume second < + for cmp := false; !cmp; { + if S.ch < 0 { + S.error(pos, "SDT not terminated") + break + } + if S.ch == '>' { + S.next() + if S.ch == '>' { + break + } + } + S.next() + } + S.next() +} + +func (S *Scanner) scanString(pos token.Position) { + // '"' already consumed + + for S.ch != '"' { + ch := S.ch + S.next() + if ch == '\n' || ch < 0 { + S.error(pos, "string not terminated") + break + } + if ch == '\\' { + S.scanEscape('"') + } + } + + S.next() +} + +func (S *Scanner) scanRawString(pos token.Position) { + // '\140' already consumed + + for S.ch != '\140' { + ch := S.ch + S.next() + if ch < 0 { + S.error(pos, "string not terminated") + break + } + } + + S.next() +} + +func (S *Scanner) skipWhitespace() { + for S.ch == ' ' || S.ch == '\t' || S.ch == '\n' || S.ch == '\r' { + S.next() + } +} + +// Helper functions for scanning multi-byte tokens such as >> += >>= . +// Different routines recognize different length tok_i based on matches +// of ch_i. If a token ends in '=', the result is tok1 or tok3 +// respectively. Otherwise, the result is tok0 if there was no other +// matching character, or tok2 if the matching character was ch2. + +func (S *Scanner) switch2(tok0, tok1 token.Type) token.Type { + if S.ch == '=' { + S.next() + return tok1 + } + return tok0 +} + +func (S *Scanner) switch3(tok0, tok1 token.Type, ch2 rune, tok2 token.Type) token.Type { + if S.ch == '=' { + S.next() + return tok1 + } + if S.ch == ch2 { + S.next() + return tok2 + } + return tok0 +} + +func (S *Scanner) switch4(tok0, tok1 token.Type, ch2 rune, tok2, tok3 token.Type) token.Type { + if S.ch == '=' { + S.next() + return tok1 + } + if S.ch == ch2 { + S.next() + if S.ch == '=' { + S.next() + return tok3 + } + return tok2 + } + return tok0 +} + +var semicolon = []byte{';'} + +// Scan scans the next token and returns the token position pos, +// the token tok, and the literal text lit corresponding to the +// token. The source end is indicated by token.EOF. +// +// For more tolerant parsing, Scan will return a valid token if +// possible even if a syntax error was encountered. Thus, even +// if the resulting token sequence contains no illegal tokens, +// a client may not assume that no error occurred. Instead it +// must check the scanner's ErrorCount or the number of calls +// of the error handler, if there was one installed. +// +func (S *Scanner) Scan() (*token.Token, token.Position) { +scanAgain: + S.skipWhitespace() + + // current token start + pos, tok := S.pos, token.ILLEGAL + + // determine token value + switch ch := S.ch; { + case isLetter(ch): + tok = S.scanIdentifier() + case isDigit(ch), ch == '.': + tok = S.scanNumber() + default: + S.next() // always make progress + switch ch { + case -1: + tok = S.tokenMap.Type("$") + case '"': + tok = S.tokenMap.Type("string_lit") + S.scanString(pos) + case '\'': + tok = S.tokenMap.Type("char") + S.scanChar(pos) + case '\140': + tok = S.tokenMap.Type("string_lit") + S.scanRawString(pos) + case ',': + tok = S.tokenMap.Type(",") + case '{': + tok = S.tokenMap.Type("{") + case '}': + tok = S.tokenMap.Type("}") + case ':': + tok = S.tokenMap.Type(":") + case ';': + tok = S.tokenMap.Type(";") + case '+': + tok = S.tokenMap.Type("+") + case '-': + if S.ch == '-' { + tok = S.tokenMap.Type("--") + S.next() + } else if S.ch == '>' { + tok = S.tokenMap.Type("->") + S.next() + } else if isDigit(S.ch) { + tok = S.scanNumber() + } else { + tok = S.tokenMap.Type("-") + } + case '=': + tok = S.tokenMap.Type("=") + case '[': + tok = S.tokenMap.Type("[") + case ']': + tok = S.tokenMap.Type("]") + case '(': + tok = S.tokenMap.Type("(") + case ')': + tok = S.tokenMap.Type(")") + case 'ε': + tok = S.tokenMap.Type("ε") + case '#': + S.scanComment(pos) + goto scanAgain + case '/': + if S.ch == '/' || S.ch == '*' { + // comment + S.scanComment(pos) + goto scanAgain + } else { + tok = S.tokenMap.Type("/") + } + case '|': + tok = S.tokenMap.Type("|") + case '<': + tok = S.scanHTML() + default: + S.error(pos, "illegal character "+charString(ch)) + } + } + //fmt.Fprintf(os.Stderr, "return tok %v, %v\n", token.NewToken(tok, S.src[pos.Offset:S.pos.Offset]), pos) + return token.NewToken(tok, S.src[pos.Offset:S.pos.Offset]), pos +} + +// An implementation of an ErrorHandler may be provided to the Scanner. +// If a syntax error is encountered and a handler was installed, Error +// is called with a position and an error message. The position points +// to the beginning of the offending token. +// +type ErrorHandler interface { + Error(pos token.Position, msg string) +} + +// Within ErrorVector, an error is represented by an Error node. The +// position Pos, if valid, points to the beginning of the offending +// token, and the error condition is described by Msg. +// +type Error struct { + Pos token.Position + Msg string +} + +func (e *Error) String() string { + if e.Pos.IsValid() { + // don't print "" + // TODO(gri) reconsider the semantics of Position.IsValid + return e.Pos.String() + ": " + e.Msg + } + return e.Msg +} diff --git a/Godeps/_workspace/src/code.google.com/p/gographviz/subgraphs.go b/Godeps/_workspace/src/code.google.com/p/gographviz/subgraphs.go new file mode 100644 index 000000000000..b22d7f22efaf --- /dev/null +++ b/Godeps/_workspace/src/code.google.com/p/gographviz/subgraphs.go @@ -0,0 +1,63 @@ +//Copyright 2013 Vastech SA (PTY) LTD +// +//Licensed under the Apache License, Version 2.0 (the "License"); +//you may not use this file except in compliance with the License. +//You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, +//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//See the License for the specific language governing permissions and +//limitations under the License. + +package gographviz + +import ( + "sort" +) + +//Represents a Subgraph. +type SubGraph struct { + Attrs Attrs + Name string +} + +//Creates a new Subgraph. +func NewSubGraph(name string) *SubGraph { + return &SubGraph{ + Attrs: make(Attrs), + Name: name, + } +} + +//Represents a set of SubGraphs. +type SubGraphs struct { + SubGraphs map[string]*SubGraph +} + +//Creates a new blank set of SubGraphs. +func NewSubGraphs() *SubGraphs { + return &SubGraphs{make(map[string]*SubGraph)} +} + +//Adds and creates a new Subgraph to the set of SubGraphs. +func (this *SubGraphs) Add(name string) { + if _, ok := this.SubGraphs[name]; !ok { + this.SubGraphs[name] = NewSubGraph(name) + } +} + +func (this *SubGraphs) Sorted() []*SubGraph { + keys := make([]string, 0) + for key := range this.SubGraphs { + keys = append(keys, key) + } + sort.Strings(keys) + s := make([]*SubGraph, len(keys)) + for i, key := range keys { + s[i] = this.SubGraphs[key] + } + return s +} diff --git a/Godeps/_workspace/src/code.google.com/p/gographviz/token/dottokens.go b/Godeps/_workspace/src/code.google.com/p/gographviz/token/dottokens.go new file mode 100644 index 000000000000..0bfff57d645b --- /dev/null +++ b/Godeps/_workspace/src/code.google.com/p/gographviz/token/dottokens.go @@ -0,0 +1,54 @@ +//Copyright 2013 Vastech SA (PTY) LTD +// +//Licensed under the Apache License, Version 2.0 (the "License"); +//you may not use this file except in compliance with the License. +//You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, +//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//See the License for the specific language governing permissions and +//limitations under the License. + +package token + +var DOTTokens = NewMapFromStrings([]string{ + "ε", + "id", + "{", + "}", + ";", + "=", + "[", + "]", + ",", + ":", + "->", + "--", + "graph", + "Graph", + "GRAPH", + "strict", + "Strict", + "STRICT", + "digraph", + "Digraph", + "DiGraph", + "DIGRAPH", + "node", + "Node", + "NODE", + "edge", + "Edge", + "EDGE", + "subgraph", + "Subgraph", + "SubGraph", + "SUBGRAPH", + "string_lit", + "int_lit", + "float_lit", + "html_lit", +}) diff --git a/Godeps/_workspace/src/code.google.com/p/gographviz/token/token.go b/Godeps/_workspace/src/code.google.com/p/gographviz/token/token.go new file mode 100644 index 000000000000..f6ec09b6f441 --- /dev/null +++ b/Godeps/_workspace/src/code.google.com/p/gographviz/token/token.go @@ -0,0 +1,242 @@ +//Copyright 2013 Vastech SA (PTY) LTD +// +//Licensed under the Apache License, Version 2.0 (the "License"); +//you may not use this file except in compliance with the License. +//You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, +//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//See the License for the specific language governing permissions and +//limitations under the License. + +package token + +import ( + "bytes" + "fmt" + "io/ioutil" + "regexp" + "strconv" + "strings" +) + +type Token struct { + Type Type + Lit []byte +} + +func NewToken(typ Type, lit []byte) *Token { + return &Token{typ, lit} +} + +func (this *Token) Equals(that *Token) bool { + if this == nil || that == nil { + return this == that + } + + if this.Type != that.Type { + return false + } + + return bytes.Equal(this.Lit, that.Lit) +} + +func (this *Token) String() string { + str := "" + if this.Type == EOF { + str += "\"$\"" + } else { + str += "\"" + string(this.Lit) + "\"" + } + str += "(" + strconv.Itoa(int(this.Type)) + ")" + return str +} + +type Type int + +const ( + ILLEGAL Type = iota - 1 + EOF +) + +func (T Type) String() string { + return strconv.Itoa(int(T)) +} + +// Position describes an arbitrary source position +// including the file, line, and column location. +// A Position is valid if the line number is > 0. +// +type Position struct { + Offset int // offset, starting at 0 + Line int // line number, starting at 1 + Column int // column number, starting at 1 (character count) +} + +// IsValid returns true if the position is valid. +func (pos *Position) IsValid() bool { return pos.Line > 0 } + +// String returns a string in one of several forms: +// +// file:line:column valid position with file name +// line:column valid position without file name +// file invalid position with file name +// - invalid position without file name +// +func (pos Position) String() string { + s := "" + if pos.IsValid() { + s += fmt.Sprintf("%d:%d", pos.Line, pos.Column) + } + if s == "" { + s = "-" + } + return s +} + +func (T *Token) IntValue() (int64, error) { + return strconv.ParseInt(string(T.Lit), 10, 64) +} + +func (T *Token) UintValue() (uint64, error) { + return strconv.ParseUint(string(T.Lit), 10, 64) +} + +func (T *Token) SDTVal() string { + sdt := string(T.Lit) + rex, err := regexp.Compile("\\$[0-9]+") + if err != nil { + panic(err) + } + idx := rex.FindAllStringIndex(sdt, -1) + res := "" + if len(idx) <= 0 { + res = sdt + } else { + for i, loc := range idx { + if loc[0] > 0 { + if i > 0 { + res += sdt[idx[i-1][1]:loc[0]] + } else { + res += sdt[0:loc[0]] + } + } + res += "X[" + res += sdt[loc[0]+1 : loc[1]] + res += "]" + } + if idx[len(idx)-1][1] < len(sdt) { + res += sdt[idx[len(idx)-1][1]:] + } + } + return strings.TrimSpace(res[2 : len(res)-2]) +} + +//*********** Tokenmap + +type TokenMap struct { + tokenMap []string + stringMap map[string]Type +} + +func NewMap() *TokenMap { + tm := &TokenMap{make([]string, 0, 10), make(map[string]Type)} + tm.AddToken("$") + tm.AddToken("ε") + return tm +} + +func (this *TokenMap) AddToken(str string) { + if _, exists := this.stringMap[str]; exists { + return + } + this.stringMap[str] = Type(len(this.tokenMap)) + this.tokenMap = append(this.tokenMap, str) +} + +func NewMapFromFile(file string) (*TokenMap, error) { + src, err := ioutil.ReadFile(file) + if err != nil { + return nil, err + } + return NewMapFromString(string(src)), nil +} + +func NewMapFromStrings(input []string) *TokenMap { + tm := NewMap() + for _, s := range input { + tm.AddToken(s) + } + return tm +} + +func NewMapFromString(input string) *TokenMap { + tokens := strings.Fields(input) + return NewMapFromStrings(tokens) +} + +func (this *TokenMap) Type(key string) Type { + tok, ok := this.stringMap[key] + if !ok { + return ILLEGAL + } + return tok +} + +func (this *TokenMap) TokenString(typ Type) string { + tok := int(typ) + if tok < 0 || tok >= len(this.tokenMap) { + return "illegal " + strconv.Itoa(tok) + } + return this.tokenMap[tok] +} + +func (this *TokenMap) String() string { + res := "" + for str, tok := range this.stringMap { + res += str + " : " + strconv.Itoa(int(tok)) + "\n" + } + return res +} + +func (this *TokenMap) Strings() []string { + return this.tokenMap[1:] +} + +func (this *TokenMap) Equals(that *TokenMap) bool { + if this == nil || that == nil { + return false + } + + if len(this.stringMap) != len(that.stringMap) || + len(this.tokenMap) != len(that.tokenMap) { + return false + } + + for str, tok := range this.stringMap { + if tok1, ok := that.stringMap[str]; !ok || tok1 != tok { + return false + } + } + + return true +} + +func (this *TokenMap) Tokens() []*Token { + res := make([]*Token, 0, len(this.stringMap)) + for typ, str := range this.tokenMap { + res = append(res, &Token{Type(typ), []byte(str)}) + } + return res +} + +func (this *TokenMap) WriteFile(file string) error { + out := "" + for i := 1; i < len(this.tokenMap); i++ { + out += this.TokenString(Type(i)) + "\n" + } + return ioutil.WriteFile(file, []byte(out), 0644) +} diff --git a/Godeps/_workspace/src/code.google.com/p/gographviz/write.go b/Godeps/_workspace/src/code.google.com/p/gographviz/write.go new file mode 100644 index 000000000000..07e51e80f39a --- /dev/null +++ b/Godeps/_workspace/src/code.google.com/p/gographviz/write.go @@ -0,0 +1,136 @@ +//Copyright 2013 Vastech SA (PTY) LTD +// +//Licensed under the Apache License, Version 2.0 (the "License"); +//you may not use this file except in compliance with the License. +//You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, software +//distributed under the License is distributed on an "AS IS" BASIS, +//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//See the License for the specific language governing permissions and +//limitations under the License. + +package gographviz + +import ( + "code.google.com/p/gographviz/ast" + "fmt" +) + +type writer struct { + *Graph + writtenLocations map[string]bool +} + +func newWriter(g *Graph) *writer { + return &writer{g, make(map[string]bool)} +} + +func appendAttrs(list ast.StmtList, attrs Attrs) ast.StmtList { + for _, name := range attrs.SortedNames() { + stmt := &ast.Attr{ + Field: ast.Id(name), + Value: ast.Id(attrs[name]), + } + list = append(list, stmt) + } + return list +} + +func (this *writer) newSubGraph(name string) *ast.SubGraph { + sub := this.SubGraphs.SubGraphs[name] + this.writtenLocations[sub.Name] = true + s := &ast.SubGraph{} + s.Id = ast.Id(sub.Name) + s.StmtList = appendAttrs(s.StmtList, sub.Attrs) + children := this.Relations.SortedChildren(name) + for _, child := range children { + s.StmtList = append(s.StmtList, this.newNodeStmt(child)) + } + return s +} + +func (this *writer) newNodeId(name string, port string) *ast.NodeId { + node := this.Nodes.Lookup[name] + return ast.MakeNodeId(node.Name, port) +} + +func (this *writer) newNodeStmt(name string) *ast.NodeStmt { + node := this.Nodes.Lookup[name] + id := ast.MakeNodeId(node.Name, "") + this.writtenLocations[node.Name] = true + return &ast.NodeStmt{ + id, + ast.PutMap(node.Attrs), + } +} + +func (this *writer) newLocation(name string, port string) ast.Location { + if this.IsNode(name) { + return this.newNodeId(name, port) + } else if this.IsSubGraph(name) { + if len(port) != 0 { + panic(fmt.Sprintf("subgraph cannot have a port: %v", port)) + } + return this.newSubGraph(name) + } + panic(fmt.Sprintf("%v is not a node or a subgraph", name)) +} + +func (this *writer) newEdgeStmt(edge *Edge) *ast.EdgeStmt { + src := this.newLocation(edge.Src, edge.SrcPort) + dst := this.newLocation(edge.Dst, edge.DstPort) + stmt := &ast.EdgeStmt{ + Source: src, + EdgeRHS: ast.EdgeRHS{ + &ast.EdgeRH{ + ast.EdgeOp(edge.Dir), + dst, + }, + }, + Attrs: ast.PutMap(edge.Attrs), + } + return stmt +} + +func (this *writer) Write() *ast.Graph { + t := &ast.Graph{} + t.Strict = this.Strict + t.Type = ast.GraphType(this.Directed) + t.Id = ast.Id(this.Name) + + t.StmtList = appendAttrs(t.StmtList, this.Attrs) + + for _, edge := range this.Edges.Edges { + t.StmtList = append(t.StmtList, this.newEdgeStmt(edge)) + } + + subGraphs := this.SubGraphs.Sorted() + for _, s := range subGraphs { + if _, ok := this.writtenLocations[s.Name]; !ok { + t.StmtList = append(t.StmtList, this.newSubGraph(s.Name)) + } + } + + nodes := this.Nodes.Sorted() + for _, n := range nodes { + if _, ok := this.writtenLocations[n.Name]; !ok { + t.StmtList = append(t.StmtList, this.newNodeStmt(n.Name)) + } + } + + return t +} + +//Creates an Abstract Syntrax Tree from the Graph. +func (g *Graph) WriteAst() *ast.Graph { + w := newWriter(g) + return w.Write() +} + +//Returns a DOT string representing the Graph. +func (g *Graph) String() string { + return g.WriteAst().String() +}