New Upstream Snapshot - golang-github-awalterschulze-gographviz

Ready changes

Summary

Merged new upstream version: 2.0.3+git20220601.1.129542d (was: 2.0.1).

Resulting package

Built on 2022-11-29T12:26 (took 3m58s)

The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:

apt install -t fresh-snapshots golang-github-awalterschulze-gographviz-dev

Lintian Result

Diff

diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 015aa52..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,10 +0,0 @@
-before_install:
-  - ./install-godeps.sh
-
-script:
-  - make travis
-
-language: go
-
-go:
-  - 1.x
diff --git a/Makefile b/Makefile
index 0e1bdb4..cf7f9e9 100644
--- a/Makefile
+++ b/Makefile
@@ -1,16 +1,33 @@
-regenerate:
+.PHONY: help regenerate test dependencies build checkers action
+
+# Prefer tools that we've installed
+export PATH := $(HOME)/go/bin:$(PATH)
+
+help:
+	@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
+
+regenerate: ## Re-generate lexers and parsers and pass through goimports
+	go get github.com/goccmack/gocc
 	go install github.com/goccmack/gocc
-	gocc -zip -o ./internal/ dot.bnf 
+	gocc -zip -o ./internal/ dot.bnf
 	find . -type f -name '*.go' | xargs goimports -w
 
-test:
+test: ## Perform package tests
 	go test ./...
 
-travis:
-	make regenerate
-	go build ./...
-	go test ./...
+dependencies: ## Grab necessary dependencies for checkers
+	go version
+	go get golang.org/x/tools/cmd/goimports
+	go get github.com/kisielk/errcheck
+	go get -u golang.org/x/lint/golint
+
+build: ## Perform build process
+	go build .
+
+checkers: ## Run all checkers (errcheck, gofmt and golint)
 	errcheck -ignore 'fmt:[FS]?[Pp]rint*' ./...
 	gofmt -l -s -w .
 	golint -set_exit_status
 	git diff --exit-code
+
+action: dependencies regenerate build test checkers ## Run steps of github action
diff --git a/Readme.md b/Readme.md
index 669c930..967b6c6 100644
--- a/Readme.md
+++ b/Readme.md
@@ -18,14 +18,16 @@ output := graph.String()
 
 ### Documentation ###
 
-The [godoc](https://godoc.org/github.com/awalterschulze/gographviz) includes some more examples.
+  - The [godoc](https://godoc.org/github.com/awalterschulze/gographviz) includes some more examples.
+  - [How to implement an anonymous subgraph](https://github.com/awalterschulze/gographviz/issues/59)
 
 ### Installation ###
 go get github.com/awalterschulze/gographviz
 
-### Tests ###
+### Build and Tests ###
+
+[![Build Status](https://github.com/awalterschulze/gographviz/workflows/build/badge.svg)](https://github.com/awalterschulze/gographviz/actions)
 
-[![Build Status](https://travis-ci.org/awalterschulze/gographviz.svg?branch=master)](https://travis-ci.org/awalterschulze/gographviz)
 
 ### Users ###
 
@@ -34,6 +36,8 @@ go get github.com/awalterschulze/gographviz
   - [imagemonkey](https://imagemonkey.io/graph?editor=true) - Let's create our own image dataset
   - [depviz](https://github.com/moul/depviz) - GitHub dependency visualizer (auto-roadmap)
   - [kustomize-graph](https://github.com/jpreese/kustomize-graph) - A tool to visualize Kustomize dependencies
+  - [inframap](https://github.com/cycloidio/inframap) - Read your tfstate or HCL to generate a graph specific for each Terraform provider
+  - [Antrea Traceflow](https://github.com/vmware-tanzu/antrea/blob/master/docs/traceflow-guide.md) supports using Traceflow for network diagnosis for Antrea, a Kubernetes networking solution intended to be Kubernetes native
 
 ### Mentions ###
 
diff --git a/analysewrite_test.go b/analysewrite_test.go
index 43164e7..a867f24 100644
--- a/analysewrite_test.go
+++ b/analysewrite_test.go
@@ -106,6 +106,11 @@ func TestString(t *testing.T) {
 		`digraph finite_state { rankdir = "LR" }`)
 }
 
+func TestAttrImgPos(t *testing.T) {
+	anal(t,
+		"digraph finite_state { imagepos = tc }")
+}
+
 func TestAttrList(t *testing.T) {
 	anal(t, `
 digraph { node [ shape = doublecircle ] }`)
diff --git a/ast/ast.go b/ast/ast.go
index 446f877..0ac9194 100644
--- a/ast/ast.go
+++ b/ast/ast.go
@@ -112,7 +112,7 @@ func (this *Graph) String() string {
 	}
 	s += this.Type.String() + " " + this.ID.String() + " {\n"
 	if this.StmtList != nil {
-		s += this.StmtList.String()
+		s += this.StmtList.indentString("\t")
 	}
 	s += "\n}\n"
 	return s
@@ -143,16 +143,21 @@ func AppendStmtList(ss, s Attrib) (StmtList, error) {
 }
 
 func (this StmtList) String() string {
+	return this.indentString("")
+}
+
+func (this StmtList) indentString(indent string) string {
 	if len(this) == 0 {
 		return ""
 	}
 	s := ""
 	for i := 0; i < len(this); i++ {
-		ss := this[i].String()
+		ss := this[i].indentString(indent)
 		if len(ss) > 0 {
-			s += "\t" + ss + ";\n"
+			s += ss + ";\n"
 		}
 	}
+	s = strings.TrimSuffix(s, "\n")
 	return s
 }
 
@@ -170,6 +175,7 @@ type Stmt interface {
 	Elem
 	Walkable
 	isStmt()
+	indentString(string) string
 }
 
 func (this NodeStmt) isStmt()   {}
@@ -216,15 +222,20 @@ func (this *SubGraph) GetPort() Port {
 }
 
 func (this *SubGraph) String() string {
+	return this.indentString("")
+}
+
+func (this *SubGraph) indentString(indent string) string {
 	gName := this.ID.String()
 	if strings.HasPrefix(gName, "anon") {
 		gName = ""
 	}
-	s := "subgraph " + this.ID.String() + " {\n"
+
+	s := indent + "subgraph " + this.ID.String() + " {\n"
 	if this.StmtList != nil {
-		s += this.StmtList.String()
+		s += this.StmtList.indentString(indent + "\t")
 	}
-	s += "\n}\n"
+	s += "\n" + indent + "}"
 	return s
 }
 
@@ -244,11 +255,15 @@ func NewEdgeAttrs(a Attrib) (EdgeAttrs, error) {
 }
 
 func (this EdgeAttrs) String() string {
+	return this.indentString("")
+}
+
+func (this EdgeAttrs) indentString(indent string) string {
 	s := AttrList(this).String()
 	if len(s) == 0 {
 		return ""
 	}
-	return `edge ` + s
+	return indent + `edge ` + s
 }
 
 func (this EdgeAttrs) Walk(v Visitor) {
@@ -268,11 +283,15 @@ func NewNodeAttrs(a Attrib) (NodeAttrs, error) {
 }
 
 func (this NodeAttrs) String() string {
+	return this.indentString("")
+}
+
+func (this NodeAttrs) indentString(indent string) string {
 	s := AttrList(this).String()
 	if len(s) == 0 {
 		return ""
 	}
-	return `node ` + s
+	return indent + `node ` + s
 }
 
 func (this NodeAttrs) Walk(v Visitor) {
@@ -292,11 +311,15 @@ func NewGraphAttrs(a Attrib) (GraphAttrs, error) {
 }
 
 func (this GraphAttrs) String() string {
+	return this.indentString("")
+}
+
+func (this GraphAttrs) indentString(indent string) string {
 	s := AttrList(this).String()
 	if len(s) == 0 {
 		return ""
 	}
-	return `graph ` + s
+	return indent + `graph ` + s
 }
 
 func (this GraphAttrs) Walk(v Visitor) {
@@ -429,7 +452,11 @@ func NewAttr(f, v Attrib) (*Attr, error) {
 }
 
 func (this *Attr) String() string {
-	return this.Field.String() + `=` + this.Value.String()
+	return this.indentString("")
+}
+
+func (this *Attr) indentString(indent string) string {
+	return indent + this.Field.String() + `=` + this.Value.String()
 }
 
 func (this *Attr) Walk(v Visitor) {
@@ -476,7 +503,11 @@ func NewEdgeStmt(id, e, attrs Attrib) (*EdgeStmt, error) {
 }
 
 func (this EdgeStmt) String() string {
-	return strings.TrimSpace(this.Source.String() + this.EdgeRHS.String() + this.Attrs.String())
+	return this.indentString("")
+}
+
+func (this EdgeStmt) indentString(indent string) string {
+	return indent + strings.TrimSpace(this.Source.String()+this.EdgeRHS.String()+` `+this.Attrs.String())
 }
 
 func (this EdgeStmt) Walk(v Visitor) {
@@ -558,7 +589,11 @@ func NewNodeStmt(id, attrs Attrib) (*NodeStmt, error) {
 }
 
 func (this NodeStmt) String() string {
-	return strings.TrimSpace(this.NodeID.String() + ` ` + this.Attrs.String())
+	return this.indentString("")
+}
+
+func (this NodeStmt) indentString(indent string) string {
+	return indent + strings.TrimSpace(this.NodeID.String()+` `+this.Attrs.String())
 }
 
 func (this NodeStmt) Walk(v Visitor) {
diff --git a/attr.go b/attr.go
index 35004fa..ee6401e 100644
--- a/attr.go
+++ b/attr.go
@@ -141,6 +141,8 @@ const (
 	Image Attr = "image"
 	// ImagePath http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:imagepath
 	ImagePath Attr = "imagepath"
+	// ImagePos https://graphviz.org/doc/info/attrs.html#d:imagepos
+	ImagePos Attr = "imagepos"
 	// ImageScale http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:imagescale
 	ImageScale Attr = "imagescale"
 	// InputScale http://graphviz.gitlab.io/_pages/doc/info/attrs.html#d:inputscale
@@ -437,6 +439,7 @@ var validAttrs = map[string]Attr{
 	string(ID):                 ID,
 	string(Image):              Image,
 	string(ImagePath):          ImagePath,
+	string(ImagePos):           ImagePos,
 	string(ImageScale):         ImageScale,
 	string(InputScale):         InputScale,
 	string(Label):              Label,
diff --git a/debian/changelog b/debian/changelog
index 9ed234b..11a7ffe 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+golang-github-awalterschulze-gographviz (2.0.3+git20220601.1.129542d-1) UNRELEASED; urgency=low
+
+  * New upstream snapshot.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Tue, 29 Nov 2022 12:23:22 -0000
+
 golang-github-awalterschulze-gographviz (2.0.1-2) unstable; urgency=medium
 
   [ Debian Janitor ]
diff --git a/escape.go b/escape.go
index 91e68d9..3b16c4f 100644
--- a/escape.go
+++ b/escape.go
@@ -78,6 +78,9 @@ func isID(s string) bool {
 		if c == '/' {
 			return false
 		}
+		if c == '.' {
+			return false
+		}
 		i++
 	}
 	return pos
diff --git a/escape_test.go b/escape_test.go
index 856d6fd..125d10f 100644
--- a/escape_test.go
+++ b/escape_test.go
@@ -46,14 +46,25 @@ func TestEscape(t *testing.T) {
 	if err := g.AddNode("asdf asdf", "a/b", nil); err != nil {
 		t.Fatal(err)
 	}
+	if err := g.AddNode("asdf asdf", "c.d", nil); err != nil {
+		t.Fatal(err)
+	}
+	if err := g.AddNode("asdf asdf", "e-f", nil); err != nil {
+		t.Fatal(err)
+	}
+	if err := g.AddNode("asdf asdf", "12_34", nil); err != nil {
+		t.Fatal(err)
+	}
 	s := g.String()
 	if !strings.HasPrefix(s, `digraph "asdf adsf" {
 	"kasdf99 99"->7;
+	"12_34";
 	"a &lt;&lt; b";
 	"a/b";
+	"c.d";
+	"e-f";
 	"kasdf99 99" [ URL="<a" ];
 	7 [ URL="<a" ];
-
 }`) {
 		t.Fatalf("%s", s)
 	}
@@ -89,19 +100,14 @@ func TestClusterSubgraphs(t *testing.T) {
 	graphStr := `graph G {
 	cluster_2--cluster_1;
 	subgraph cluster0 {
-	subgraph cluster_1 {
-
-}
-;
-	subgraph cluster_2 {
+		subgraph cluster_1 {
 
-}
-;
+		};
+		subgraph cluster_2 {
 
-}
-;
+		};
+	};
 	"Code deployment";
-
 }`
 	if !strings.HasPrefix(s, graphStr) {
 		t.Fatalf("%s", s)
diff --git a/example_test.go b/example_test.go
index aa7e6ec..5e998bb 100644
--- a/example_test.go
+++ b/example_test.go
@@ -30,7 +30,6 @@ func ExampleRead() {
 	//	Hello->World;
 	//	Hello;
 	//	World;
-	//
 	//}
 }
 
@@ -57,7 +56,6 @@ func ExampleNewGraph() {
 	//	Hello->World;
 	//	Hello;
 	//	World;
-	//
 	//}
 }
 
@@ -160,26 +158,25 @@ func ExampleMyOwnGraphStructure() {
 	s := output.String()
 	fmt.Println(s)
 	// Output: digraph matrix {
-	//	1->1[ label=0 ];
-	//	1->2[ label=5 ];
-	//	1->3[ label=0 ];
-	//	1->4[ label=0 ];
-	//	2->1[ label=0 ];
-	//	2->2[ label=0 ];
-	//	2->3[ label=0 ];
-	//	2->4[ label=0 ];
-	//	3->1[ label=0 ];
-	//	3->2[ label=0 ];
-	//	3->3[ label=0 ];
-	//	3->4[ label=0 ];
-	//	4->1[ label=2 ];
-	//	4->2[ label=1 ];
-	//	4->3[ label=0 ];
-	//	4->4[ label=0 ];
+	//	1->1 [ label=0 ];
+	//	1->2 [ label=5 ];
+	//	1->3 [ label=0 ];
+	//	1->4 [ label=0 ];
+	//	2->1 [ label=0 ];
+	//	2->2 [ label=0 ];
+	//	2->3 [ label=0 ];
+	//	2->4 [ label=0 ];
+	//	3->1 [ label=0 ];
+	//	3->2 [ label=0 ];
+	//	3->3 [ label=0 ];
+	//	3->4 [ label=0 ];
+	//	4->1 [ label=2 ];
+	//	4->2 [ label=1 ];
+	//	4->3 [ label=0 ];
+	//	4->4 [ label=0 ];
 	//	1;
 	//	2;
 	//	3;
 	//	4;
-	//
 	//}
 }
diff --git a/install-godeps.sh b/install-godeps.sh
deleted file mode 100755
index d5878ae..0000000
--- a/install-godeps.sh
+++ /dev/null
@@ -1,7 +0,0 @@
-#!/usr/bin/env bash
-set -xe
-mkdir -p $GOPATH/src/githbub.com/goccmack
-git clone https://github.com/goccmack/gocc $GOPATH/src/github.com/goccmack/gocc
-go get golang.org/x/tools/cmd/goimports
-go get github.com/kisielk/errcheck
-go get -u golang.org/x/lint/golint
\ No newline at end of file
diff --git a/internal/errors/errors.go b/internal/errors/errors.go
index 3df79f0..9dc8c03 100644
--- a/internal/errors/errors.go
+++ b/internal/errors/errors.go
@@ -4,7 +4,9 @@ package errors
 
 import (
 	"fmt"
+	"strconv"
 	"strings"
+	"unicode"
 
 	"github.com/awalterschulze/gographviz/internal/token"
 )
@@ -22,35 +24,85 @@ type Error struct {
 
 func (e *Error) String() string {
 	w := new(strings.Builder)
-	fmt.Fprintf(w, "Error")
 	if e.Err != nil {
-		fmt.Fprintf(w, " %s\n", e.Err)
+		fmt.Fprintln(w, "Error ", e.Err)
 	} else {
-		fmt.Fprintf(w, "\n")
+		fmt.Fprintln(w, "Error")
 	}
 	fmt.Fprintf(w, "Token: type=%d, lit=%s\n", e.ErrorToken.Type, e.ErrorToken.Lit)
 	fmt.Fprintf(w, "Pos: offset=%d, line=%d, column=%d\n", e.ErrorToken.Pos.Offset, e.ErrorToken.Pos.Line, e.ErrorToken.Pos.Column)
-	fmt.Fprintf(w, "Expected one of: ")
+	fmt.Fprint(w, "Expected one of: ")
 	for _, sym := range e.ExpectedTokens {
-		fmt.Fprintf(w, "%s ", sym)
+		fmt.Fprint(w, string(sym), " ")
 	}
-	fmt.Fprintf(w, "ErrorSymbol:\n")
+	fmt.Fprintln(w, "ErrorSymbol:")
 	for _, sym := range e.ErrorSymbols {
 		fmt.Fprintf(w, "%v\n", sym)
 	}
+
 	return w.String()
 }
 
+func DescribeExpected(tokens []string) string {
+	switch len(tokens) {
+	case 0:
+		return "unexpected additional tokens"
+
+	case 1:
+		return "expected " + tokens[0]
+
+	case 2:
+		return "expected either " + tokens[0] + " or " + tokens[1]
+
+	case 3:
+		// Oxford-comma rules require more than 3 items in a list for the
+		// comma to appear before the 'or'
+		return fmt.Sprintf("expected one of %s, %s or %s", tokens[0], tokens[1], tokens[2])
+
+	default:
+		// Oxford-comma separated alternatives list.
+		tokens = append(tokens[:len(tokens)-1], "or "+tokens[len(tokens)-1])
+		return "expected one of " + strings.Join(tokens, ", ")
+	}
+}
+
+func DescribeToken(tok *token.Token) string {
+	switch tok.Type {
+	case token.INVALID:
+		return fmt.Sprintf("unknown/invalid token %q", tok.Lit)
+	case token.EOF:
+		return "end-of-file"
+	default:
+		return fmt.Sprintf("%q", tok.Lit)
+	}
+}
+
 func (e *Error) Error() string {
-	w := new(strings.Builder)
-	fmt.Fprintf(w, "Error in S%d: %s, %s", e.StackTop, token.TokMap.TokenString(e.ErrorToken), e.ErrorToken.Pos.String())
+	// identify the line and column of the error in 'gnu' style so it can be understood
+	// by editors and IDEs; user will need to prefix it with a filename.
+	text := fmt.Sprintf("%d:%d: error: ", e.ErrorToken.Pos.Line, e.ErrorToken.Pos.Column)
+
+	// See if the error token can provide us with the filename.
+	switch src := e.ErrorToken.Pos.Context.(type) {
+	case token.Sourcer:
+		text = src.Source() + ":" + text
+	}
+
 	if e.Err != nil {
-		fmt.Fprintf(w, ": %+v", e.Err)
+		// Custom error specified, e.g. by << nil, errors.New("missing newline") >>
+		text += e.Err.Error()
 	} else {
-		fmt.Fprintf(w, ", expected one of: ")
-		for _, expected := range e.ExpectedTokens {
-			fmt.Fprintf(w, "%s ", expected)
+		tokens := make([]string, len(e.ExpectedTokens))
+		for idx, token := range e.ExpectedTokens {
+			if !unicode.IsLetter(rune(token[0])) {
+				token = strconv.Quote(token)
+			}
+			tokens[idx] = token
 		}
+		text += DescribeExpected(tokens)
+		actual := DescribeToken(e.ErrorToken)
+		text += fmt.Sprintf("; got: %s", actual)
 	}
-	return w.String()
+
+	return text
 }
diff --git a/internal/lexer/lexer.go b/internal/lexer/lexer.go
index c967f28..31f7ceb 100644
--- a/internal/lexer/lexer.go
+++ b/internal/lexer/lexer.go
@@ -16,35 +16,50 @@ const (
 )
 
 type Lexer struct {
-	src    []byte
-	pos    int
-	line   int
-	column int
+	src     []byte
+	pos     int
+	line    int
+	column  int
+	Context token.Context
 }
 
 func NewLexer(src []byte) *Lexer {
 	lexer := &Lexer{
-		src:    src,
-		pos:    0,
-		line:   1,
-		column: 1,
+		src:     src,
+		pos:     0,
+		line:    1,
+		column:  1,
+		Context: nil,
 	}
 	return lexer
 }
 
+// SourceContext is a simple instance of a token.Context which
+// contains the name of the source file.
+type SourceContext struct {
+	Filepath string
+}
+
+func (s *SourceContext) Source() string {
+	return s.Filepath
+}
+
 func NewLexerFile(fpath string) (*Lexer, error) {
 	src, err := ioutil.ReadFile(fpath)
 	if err != nil {
 		return nil, err
 	}
-	return NewLexer(src), nil
+	lexer := NewLexer(src)
+	lexer.Context = &SourceContext{Filepath: fpath}
+	return lexer, nil
 }
 
 func (l *Lexer) Scan() (tok *token.Token) {
-	tok = new(token.Token)
+	tok = &token.Token{}
 	if l.pos >= len(l.src) {
 		tok.Type = token.EOF
 		tok.Pos.Offset, tok.Pos.Line, tok.Pos.Column = l.pos, l.line, l.column
+		tok.Pos.Context = l.Context
 		return
 	}
 	start, startLine, startColumn, end := l.pos, l.line, l.column, 0
@@ -103,6 +118,7 @@ func (l *Lexer) Scan() (tok *token.Token) {
 		tok.Lit = []byte{}
 	}
 	tok.Pos.Offset, tok.Pos.Line, tok.Pos.Column = start, startLine, startColumn
+	tok.Pos.Context = l.Context
 
 	return
 }
diff --git a/internal/parser/context.go b/internal/parser/context.go
new file mode 100644
index 0000000..3ed954a
--- /dev/null
+++ b/internal/parser/context.go
@@ -0,0 +1,7 @@
+// Code generated by gocc; DO NOT EDIT.
+
+package parser
+
+// Parser-specific user-defined and entirely-optional context,
+// accessible as '$Context' in SDT actions.
+type Context interface{}
diff --git a/internal/parser/parser.go b/internal/parser/parser.go
index 9a5338f..026cf83 100644
--- a/internal/parser/parser.go
+++ b/internal/parser/parser.go
@@ -91,6 +91,7 @@ type Parser struct {
 	stack     *stack
 	nextToken *token.Token
 	pos       int
+	Context   Context
 }
 
 type Scanner interface {
@@ -202,7 +203,7 @@ func (p *Parser) Parse(scanner Scanner) (res interface{}, err error) {
 			p.nextToken = scanner.Scan()
 		case reduce:
 			prod := productionsTable[int(act)]
-			attrib, err := prod.ReduceFunc(p.stack.popN(prod.NumSymbols))
+			attrib, err := prod.ReduceFunc(p.stack.popN(prod.NumSymbols), p.Context)
 			if err != nil {
 				return nil, p.newError(err)
 			} else {
diff --git a/internal/parser/productionstable.go b/internal/parser/productionstable.go
index 3262aef..1594b40 100644
--- a/internal/parser/productionstable.go
+++ b/internal/parser/productionstable.go
@@ -5,7 +5,6 @@ package parser
 import "github.com/awalterschulze/gographviz/ast"
 
 type (
-	//TODO: change type and variable names to be consistent with other tables
 	ProdTab      [numProductions]ProdTabEntry
 	ProdTabEntry struct {
 		String     string
@@ -13,7 +12,7 @@ type (
 		NTType     int
 		Index      int
 		NumSymbols int
-		ReduceFunc func([]Attrib) (Attrib, error)
+		ReduceFunc func([]Attrib, interface{}) (Attrib, error)
 	}
 	Attrib interface {
 	}
@@ -26,7 +25,7 @@ var productionsTable = ProdTab{
 		NTType:     0,
 		Index:      0,
 		NumSymbols: 1,
-		ReduceFunc: func(X []Attrib) (Attrib, error) {
+		ReduceFunc: func(X []Attrib, C interface{}) (Attrib, error) {
 			return X[0], nil
 		},
 	},
@@ -36,7 +35,7 @@ var productionsTable = ProdTab{
 		NTType:     1,
 		Index:      1,
 		NumSymbols: 3,
-		ReduceFunc: func(X []Attrib) (Attrib, error) {
+		ReduceFunc: func(X []Attrib, C interface{}) (Attrib, error) {
 			return ast.NewGraph(ast.GRAPH, ast.FALSE, nil, nil)
 		},
 	},
@@ -46,7 +45,7 @@ var productionsTable = ProdTab{
 		NTType:     1,
 		Index:      2,
 		NumSymbols: 4,
-		ReduceFunc: func(X []Attrib) (Attrib, error) {
+		ReduceFunc: func(X []Attrib, C interface{}) (Attrib, error) {
 			return ast.NewGraph(ast.GRAPH, ast.TRUE, nil, nil)
 		},
 	},
@@ -56,7 +55,7 @@ var productionsTable = ProdTab{
 		NTType:     1,
 		Index:      3,
 		NumSymbols: 4,
-		ReduceFunc: func(X []Attrib) (Attrib, error) {
+		ReduceFunc: func(X []Attrib, C interface{}) (Attrib, error) {
 			return ast.NewGraph(ast.GRAPH, ast.FALSE, X[1], nil)
 		},
 	},
@@ -66,7 +65,7 @@ var productionsTable = ProdTab{
 		NTType:     1,
 		Index:      4,
 		NumSymbols: 5,
-		ReduceFunc: func(X []Attrib) (Attrib, error) {
+		ReduceFunc: func(X []Attrib, C interface{}) (Attrib, error) {
 			return ast.NewGraph(ast.GRAPH, ast.TRUE, X[2], nil)
 		},
 	},
@@ -76,7 +75,7 @@ var productionsTable = ProdTab{
 		NTType:     1,
 		Index:      5,
 		NumSymbols: 4,
-		ReduceFunc: func(X []Attrib) (Attrib, error) {
+		ReduceFunc: func(X []Attrib, C interface{}) (Attrib, error) {
 			return ast.NewGraph(ast.GRAPH, ast.FALSE, nil, X[2])
 		},
 	},
@@ -86,7 +85,7 @@ var productionsTable = ProdTab{
 		NTType:     1,
 		Index:      6,
 		NumSymbols: 5,
-		ReduceFunc: func(X []Attrib) (Attrib, error) {
+		ReduceFunc: func(X []Attrib, C interface{}) (Attrib, error) {
 			return ast.NewGraph(ast.GRAPH, ast.FALSE, X[1], X[3])
 		},
 	},
@@ -96,7 +95,7 @@ var productionsTable = ProdTab{
 		NTType:     1,
 		Index:      7,
 		NumSymbols: 5,
-		ReduceFunc: func(X []Attrib) (Attrib, error) {
+		ReduceFunc: func(X []Attrib, C interface{}) (Attrib, error) {
 			return ast.NewGraph(ast.GRAPH, ast.TRUE, nil, X[3])
 		},
 	},
@@ -106,7 +105,7 @@ var productionsTable = ProdTab{
 		NTType:     1,
 		Index:      8,
 		NumSymbols: 6,
-		ReduceFunc: func(X []Attrib) (Attrib, error) {
+		ReduceFunc: func(X []Attrib, C interface{}) (Attrib, error) {
 			return ast.NewGraph(ast.GRAPH, ast.TRUE, X[2], X[4])
 		},
 	},
@@ -116,7 +115,7 @@ var productionsTable = ProdTab{
 		NTType:     1,
 		Index:      9,
 		NumSymbols: 3,
-		ReduceFunc: func(X []Attrib) (Attrib, error) {
+		ReduceFunc: func(X []Attrib, C interface{}) (Attrib, error) {
 			return ast.NewGraph(ast.DIGRAPH, ast.FALSE, nil, nil)
 		},
 	},
@@ -126,7 +125,7 @@ var productionsTable = ProdTab{
 		NTType:     1,
 		Index:      10,
 		NumSymbols: 4,
-		ReduceFunc: func(X []Attrib) (Attrib, error) {
+		ReduceFunc: func(X []Attrib, C interface{}) (Attrib, error) {
 			return ast.NewGraph(ast.DIGRAPH, ast.TRUE, nil, nil)
 		},
 	},
@@ -136,7 +135,7 @@ var productionsTable = ProdTab{
 		NTType:     1,
 		Index:      11,
 		NumSymbols: 4,
-		ReduceFunc: func(X []Attrib) (Attrib, error) {
+		ReduceFunc: func(X []Attrib, C interface{}) (Attrib, error) {
 			return ast.NewGraph(ast.DIGRAPH, ast.FALSE, X[1], nil)
 		},
 	},
@@ -146,7 +145,7 @@ var productionsTable = ProdTab{
 		NTType:     1,
 		Index:      12,
 		NumSymbols: 5,
-		ReduceFunc: func(X []Attrib) (Attrib, error) {
+		ReduceFunc: func(X []Attrib, C interface{}) (Attrib, error) {
 			return ast.NewGraph(ast.DIGRAPH, ast.TRUE, X[2], nil)
 		},
 	},
@@ -156,7 +155,7 @@ var productionsTable = ProdTab{
 		NTType:     1,
 		Index:      13,
 		NumSymbols: 4,
-		ReduceFunc: func(X []Attrib) (Attrib, error) {
+		ReduceFunc: func(X []Attrib, C interface{}) (Attrib, error) {
 			return ast.NewGraph(ast.DIGRAPH, ast.FALSE, nil, X[2])
 		},
 	},
@@ -166,7 +165,7 @@ var productionsTable = ProdTab{
 		NTType:     1,
 		Index:      14,
 		NumSymbols: 5,
-		ReduceFunc: func(X []Attrib) (Attrib, error) {
+		ReduceFunc: func(X []Attrib, C interface{}) (Attrib, error) {
 			return ast.NewGraph(ast.DIGRAPH, ast.FALSE, X[1], X[3])
 		},
 	},
@@ -176,7 +175,7 @@ var productionsTable = ProdTab{
 		NTType:     1,
 		Index:      15,
 		NumSymbols: 5,
-		ReduceFunc: func(X []Attrib) (Attrib, error) {
+		ReduceFunc: func(X []Attrib, C interface{}) (Attrib, error) {
 			return ast.NewGraph(ast.DIGRAPH, ast.TRUE, nil, X[3])
 		},
 	},
@@ -186,7 +185,7 @@ var productionsTable = ProdTab{
 		NTType:     1,
 		Index:      16,
 		NumSymbols: 6,
-		ReduceFunc: func(X []Attrib) (Attrib, error) {
+		ReduceFunc: func(X []Attrib, C interface{}) (Attrib, error) {
 			return ast.NewGraph(ast.DIGRAPH, ast.TRUE, X[2], X[4])
 		},
 	},
@@ -196,7 +195,7 @@ var productionsTable = ProdTab{
 		NTType:     2,
 		Index:      17,
 		NumSymbols: 1,
-		ReduceFunc: func(X []Attrib) (Attrib, error) {
+		ReduceFunc: func(X []Attrib, C interface{}) (Attrib, error) {
 			return ast.NewStmtList(X[0])
 		},
 	},
@@ -206,7 +205,7 @@ var productionsTable = ProdTab{
 		NTType:     2,
 		Index:      18,
 		NumSymbols: 2,
-		ReduceFunc: func(X []Attrib) (Attrib, error) {
+		ReduceFunc: func(X []Attrib, C interface{}) (Attrib, error) {
 			return ast.AppendStmtList(X[0], X[1])
 		},
 	},
@@ -216,7 +215,7 @@ var productionsTable = ProdTab{
 		NTType:     3,
 		Index:      19,
 		NumSymbols: 1,
-		ReduceFunc: func(X []Attrib) (Attrib, error) {
+		ReduceFunc: func(X []Attrib, C interface{}) (Attrib, error) {
 			return X[0], nil
 		},
 	},
@@ -226,7 +225,7 @@ var productionsTable = ProdTab{
 		NTType:     3,
 		Index:      20,
 		NumSymbols: 2,
-		ReduceFunc: func(X []Attrib) (Attrib, error) {
+		ReduceFunc: func(X []Attrib, C interface{}) (Attrib, error) {
 			return X[0], nil
 		},
 	},
@@ -236,7 +235,7 @@ var productionsTable = ProdTab{
 		NTType:     4,
 		Index:      21,
 		NumSymbols: 3,
-		ReduceFunc: func(X []Attrib) (Attrib, error) {
+		ReduceFunc: func(X []Attrib, C interface{}) (Attrib, error) {
 			return ast.NewAttr(X[0], X[2])
 		},
 	},
@@ -246,7 +245,7 @@ var productionsTable = ProdTab{
 		NTType:     4,
 		Index:      22,
 		NumSymbols: 1,
-		ReduceFunc: func(X []Attrib) (Attrib, error) {
+		ReduceFunc: func(X []Attrib, C interface{}) (Attrib, error) {
 			return X[0], nil
 		},
 	},
@@ -256,7 +255,7 @@ var productionsTable = ProdTab{
 		NTType:     4,
 		Index:      23,
 		NumSymbols: 1,
-		ReduceFunc: func(X []Attrib) (Attrib, error) {
+		ReduceFunc: func(X []Attrib, C interface{}) (Attrib, error) {
 			return X[0], nil
 		},
 	},
@@ -266,7 +265,7 @@ var productionsTable = ProdTab{
 		NTType:     4,
 		Index:      24,
 		NumSymbols: 1,
-		ReduceFunc: func(X []Attrib) (Attrib, error) {
+		ReduceFunc: func(X []Attrib, C interface{}) (Attrib, error) {
 			return X[0], nil
 		},
 	},
@@ -276,7 +275,7 @@ var productionsTable = ProdTab{
 		NTType:     4,
 		Index:      25,
 		NumSymbols: 1,
-		ReduceFunc: func(X []Attrib) (Attrib, error) {
+		ReduceFunc: func(X []Attrib, C interface{}) (Attrib, error) {
 			return X[0], nil
 		},
 	},
@@ -286,7 +285,7 @@ var productionsTable = ProdTab{
 		NTType:     5,
 		Index:      26,
 		NumSymbols: 2,
-		ReduceFunc: func(X []Attrib) (Attrib, error) {
+		ReduceFunc: func(X []Attrib, C interface{}) (Attrib, error) {
 			return ast.NewGraphAttrs(X[1])
 		},
 	},
@@ -296,7 +295,7 @@ var productionsTable = ProdTab{
 		NTType:     5,
 		Index:      27,
 		NumSymbols: 2,
-		ReduceFunc: func(X []Attrib) (Attrib, error) {
+		ReduceFunc: func(X []Attrib, C interface{}) (Attrib, error) {
 			return ast.NewNodeAttrs(X[1])
 		},
 	},
@@ -306,7 +305,7 @@ var productionsTable = ProdTab{
 		NTType:     5,
 		Index:      28,
 		NumSymbols: 2,
-		ReduceFunc: func(X []Attrib) (Attrib, error) {
+		ReduceFunc: func(X []Attrib, C interface{}) (Attrib, error) {
 			return ast.NewEdgeAttrs(X[1])
 		},
 	},
@@ -316,7 +315,7 @@ var productionsTable = ProdTab{
 		NTType:     6,
 		Index:      29,
 		NumSymbols: 2,
-		ReduceFunc: func(X []Attrib) (Attrib, error) {
+		ReduceFunc: func(X []Attrib, C interface{}) (Attrib, error) {
 			return ast.NewAttrList(nil)
 		},
 	},
@@ -326,7 +325,7 @@ var productionsTable = ProdTab{
 		NTType:     6,
 		Index:      30,
 		NumSymbols: 3,
-		ReduceFunc: func(X []Attrib) (Attrib, error) {
+		ReduceFunc: func(X []Attrib, C interface{}) (Attrib, error) {
 			return ast.NewAttrList(X[1])
 		},
 	},
@@ -336,7 +335,7 @@ var productionsTable = ProdTab{
 		NTType:     6,
 		Index:      31,
 		NumSymbols: 3,
-		ReduceFunc: func(X []Attrib) (Attrib, error) {
+		ReduceFunc: func(X []Attrib, C interface{}) (Attrib, error) {
 			return ast.AppendAttrList(X[0], nil)
 		},
 	},
@@ -346,7 +345,7 @@ var productionsTable = ProdTab{
 		NTType:     6,
 		Index:      32,
 		NumSymbols: 4,
-		ReduceFunc: func(X []Attrib) (Attrib, error) {
+		ReduceFunc: func(X []Attrib, C interface{}) (Attrib, error) {
 			return ast.AppendAttrList(X[0], X[2])
 		},
 	},
@@ -356,7 +355,7 @@ var productionsTable = ProdTab{
 		NTType:     7,
 		Index:      33,
 		NumSymbols: 1,
-		ReduceFunc: func(X []Attrib) (Attrib, error) {
+		ReduceFunc: func(X []Attrib, C interface{}) (Attrib, error) {
 			return ast.NewAList(X[0])
 		},
 	},
@@ -366,7 +365,7 @@ var productionsTable = ProdTab{
 		NTType:     7,
 		Index:      34,
 		NumSymbols: 2,
-		ReduceFunc: func(X []Attrib) (Attrib, error) {
+		ReduceFunc: func(X []Attrib, C interface{}) (Attrib, error) {
 			return ast.AppendAList(X[0], X[1])
 		},
 	},
@@ -376,7 +375,7 @@ var productionsTable = ProdTab{
 		NTType:     7,
 		Index:      35,
 		NumSymbols: 3,
-		ReduceFunc: func(X []Attrib) (Attrib, error) {
+		ReduceFunc: func(X []Attrib, C interface{}) (Attrib, error) {
 			return ast.AppendAList(X[0], X[2])
 		},
 	},
@@ -386,7 +385,7 @@ var productionsTable = ProdTab{
 		NTType:     8,
 		Index:      36,
 		NumSymbols: 1,
-		ReduceFunc: func(X []Attrib) (Attrib, error) {
+		ReduceFunc: func(X []Attrib, C interface{}) (Attrib, error) {
 			return ast.NewAttr(X[0], nil)
 		},
 	},
@@ -396,7 +395,7 @@ var productionsTable = ProdTab{
 		NTType:     8,
 		Index:      37,
 		NumSymbols: 3,
-		ReduceFunc: func(X []Attrib) (Attrib, error) {
+		ReduceFunc: func(X []Attrib, C interface{}) (Attrib, error) {
 			return ast.NewAttr(X[0], X[2])
 		},
 	},
@@ -406,7 +405,7 @@ var productionsTable = ProdTab{
 		NTType:     9,
 		Index:      38,
 		NumSymbols: 2,
-		ReduceFunc: func(X []Attrib) (Attrib, error) {
+		ReduceFunc: func(X []Attrib, C interface{}) (Attrib, error) {
 			return ast.NewEdgeStmt(X[0], X[1], nil)
 		},
 	},
@@ -416,7 +415,7 @@ var productionsTable = ProdTab{
 		NTType:     9,
 		Index:      39,
 		NumSymbols: 3,
-		ReduceFunc: func(X []Attrib) (Attrib, error) {
+		ReduceFunc: func(X []Attrib, C interface{}) (Attrib, error) {
 			return ast.NewEdgeStmt(X[0], X[1], X[2])
 		},
 	},
@@ -426,7 +425,7 @@ var productionsTable = ProdTab{
 		NTType:     9,
 		Index:      40,
 		NumSymbols: 2,
-		ReduceFunc: func(X []Attrib) (Attrib, error) {
+		ReduceFunc: func(X []Attrib, C interface{}) (Attrib, error) {
 			return ast.NewEdgeStmt(X[0], X[1], nil)
 		},
 	},
@@ -436,7 +435,7 @@ var productionsTable = ProdTab{
 		NTType:     9,
 		Index:      41,
 		NumSymbols: 3,
-		ReduceFunc: func(X []Attrib) (Attrib, error) {
+		ReduceFunc: func(X []Attrib, C interface{}) (Attrib, error) {
 			return ast.NewEdgeStmt(X[0], X[1], X[2])
 		},
 	},
@@ -446,7 +445,7 @@ var productionsTable = ProdTab{
 		NTType:     10,
 		Index:      42,
 		NumSymbols: 2,
-		ReduceFunc: func(X []Attrib) (Attrib, error) {
+		ReduceFunc: func(X []Attrib, C interface{}) (Attrib, error) {
 			return ast.NewEdgeRHS(X[0], X[1])
 		},
 	},
@@ -456,7 +455,7 @@ var productionsTable = ProdTab{
 		NTType:     10,
 		Index:      43,
 		NumSymbols: 2,
-		ReduceFunc: func(X []Attrib) (Attrib, error) {
+		ReduceFunc: func(X []Attrib, C interface{}) (Attrib, error) {
 			return ast.NewEdgeRHS(X[0], X[1])
 		},
 	},
@@ -466,7 +465,7 @@ var productionsTable = ProdTab{
 		NTType:     10,
 		Index:      44,
 		NumSymbols: 3,
-		ReduceFunc: func(X []Attrib) (Attrib, error) {
+		ReduceFunc: func(X []Attrib, C interface{}) (Attrib, error) {
 			return ast.AppendEdgeRHS(X[0], X[1], X[2])
 		},
 	},
@@ -476,7 +475,7 @@ var productionsTable = ProdTab{
 		NTType:     10,
 		Index:      45,
 		NumSymbols: 3,
-		ReduceFunc: func(X []Attrib) (Attrib, error) {
+		ReduceFunc: func(X []Attrib, C interface{}) (Attrib, error) {
 			return ast.AppendEdgeRHS(X[0], X[1], X[2])
 		},
 	},
@@ -486,7 +485,7 @@ var productionsTable = ProdTab{
 		NTType:     11,
 		Index:      46,
 		NumSymbols: 1,
-		ReduceFunc: func(X []Attrib) (Attrib, error) {
+		ReduceFunc: func(X []Attrib, C interface{}) (Attrib, error) {
 			return ast.NewNodeStmt(X[0], nil)
 		},
 	},
@@ -496,7 +495,7 @@ var productionsTable = ProdTab{
 		NTType:     11,
 		Index:      47,
 		NumSymbols: 2,
-		ReduceFunc: func(X []Attrib) (Attrib, error) {
+		ReduceFunc: func(X []Attrib, C interface{}) (Attrib, error) {
 			return ast.NewNodeStmt(X[0], X[1])
 		},
 	},
@@ -506,7 +505,7 @@ var productionsTable = ProdTab{
 		NTType:     12,
 		Index:      48,
 		NumSymbols: 1,
-		ReduceFunc: func(X []Attrib) (Attrib, error) {
+		ReduceFunc: func(X []Attrib, C interface{}) (Attrib, error) {
 			return ast.NewNodeID(X[0], nil)
 		},
 	},
@@ -516,7 +515,7 @@ var productionsTable = ProdTab{
 		NTType:     12,
 		Index:      49,
 		NumSymbols: 2,
-		ReduceFunc: func(X []Attrib) (Attrib, error) {
+		ReduceFunc: func(X []Attrib, C interface{}) (Attrib, error) {
 			return ast.NewNodeID(X[0], X[1])
 		},
 	},
@@ -526,7 +525,7 @@ var productionsTable = ProdTab{
 		NTType:     13,
 		Index:      50,
 		NumSymbols: 2,
-		ReduceFunc: func(X []Attrib) (Attrib, error) {
+		ReduceFunc: func(X []Attrib, C interface{}) (Attrib, error) {
 			return ast.NewPort(X[1], nil), nil
 		},
 	},
@@ -536,7 +535,7 @@ var productionsTable = ProdTab{
 		NTType:     13,
 		Index:      51,
 		NumSymbols: 4,
-		ReduceFunc: func(X []Attrib) (Attrib, error) {
+		ReduceFunc: func(X []Attrib, C interface{}) (Attrib, error) {
 			return ast.NewPort(X[1], X[3]), nil
 		},
 	},
@@ -546,7 +545,7 @@ var productionsTable = ProdTab{
 		NTType:     14,
 		Index:      52,
 		NumSymbols: 3,
-		ReduceFunc: func(X []Attrib) (Attrib, error) {
+		ReduceFunc: func(X []Attrib, C interface{}) (Attrib, error) {
 			return ast.NewSubGraph(nil, X[1])
 		},
 	},
@@ -556,7 +555,7 @@ var productionsTable = ProdTab{
 		NTType:     14,
 		Index:      53,
 		NumSymbols: 4,
-		ReduceFunc: func(X []Attrib) (Attrib, error) {
+		ReduceFunc: func(X []Attrib, C interface{}) (Attrib, error) {
 			return ast.NewSubGraph(nil, X[2])
 		},
 	},
@@ -566,7 +565,7 @@ var productionsTable = ProdTab{
 		NTType:     14,
 		Index:      54,
 		NumSymbols: 5,
-		ReduceFunc: func(X []Attrib) (Attrib, error) {
+		ReduceFunc: func(X []Attrib, C interface{}) (Attrib, error) {
 			return ast.NewSubGraph(X[1], X[3])
 		},
 	},
@@ -576,7 +575,7 @@ var productionsTable = ProdTab{
 		NTType:     14,
 		Index:      55,
 		NumSymbols: 3,
-		ReduceFunc: func(X []Attrib) (Attrib, error) {
+		ReduceFunc: func(X []Attrib, C interface{}) (Attrib, error) {
 			return ast.NewSubGraph(nil, nil)
 		},
 	},
@@ -586,7 +585,7 @@ var productionsTable = ProdTab{
 		NTType:     14,
 		Index:      56,
 		NumSymbols: 4,
-		ReduceFunc: func(X []Attrib) (Attrib, error) {
+		ReduceFunc: func(X []Attrib, C interface{}) (Attrib, error) {
 			return ast.NewSubGraph(X[1], nil)
 		},
 	},
@@ -596,7 +595,7 @@ var productionsTable = ProdTab{
 		NTType:     15,
 		Index:      57,
 		NumSymbols: 1,
-		ReduceFunc: func(X []Attrib) (Attrib, error) {
+		ReduceFunc: func(X []Attrib, C interface{}) (Attrib, error) {
 			return ast.DIRECTED, nil
 		},
 	},
@@ -606,7 +605,7 @@ var productionsTable = ProdTab{
 		NTType:     15,
 		Index:      58,
 		NumSymbols: 1,
-		ReduceFunc: func(X []Attrib) (Attrib, error) {
+		ReduceFunc: func(X []Attrib, C interface{}) (Attrib, error) {
 			return ast.UNDIRECTED, nil
 		},
 	},
@@ -616,7 +615,7 @@ var productionsTable = ProdTab{
 		NTType:     16,
 		Index:      59,
 		NumSymbols: 1,
-		ReduceFunc: func(X []Attrib) (Attrib, error) {
+		ReduceFunc: func(X []Attrib, C interface{}) (Attrib, error) {
 			return ast.NewID(X[0])
 		},
 	},
diff --git a/internal/token/context.go b/internal/token/context.go
new file mode 100644
index 0000000..0f4e420
--- /dev/null
+++ b/internal/token/context.go
@@ -0,0 +1,14 @@
+// Code generated by gocc; DO NOT EDIT.
+
+package token
+
+// Context allows user-defined data to be associated with the
+// lexer/scanner to be associated with each token that lexer
+// produces.
+type Context interface{}
+
+// Sourcer is a Context interface which presents a Source() method
+// identifying e.g the filename for the current code.
+type Sourcer interface {
+	Source() string
+}
diff --git a/internal/token/token.go b/internal/token/token.go
index 873fe8e..3ea9217 100644
--- a/internal/token/token.go
+++ b/internal/token/token.go
@@ -3,7 +3,10 @@
 package token
 
 import (
+	"bytes"
 	"fmt"
+	"strconv"
+	"unicode/utf8"
 )
 
 type Token struct {
@@ -20,13 +23,20 @@ const (
 )
 
 type Pos struct {
-	Offset int
-	Line   int
-	Column int
+	Offset  int
+	Line    int
+	Column  int
+	Context Context
 }
 
 func (p Pos) String() string {
-	return fmt.Sprintf("Pos(offset=%d, line=%d, column=%d)", p.Offset, p.Line, p.Column)
+	// If the context provides a filename, provide a human-readable File:Line:Column representation.
+	switch src := p.Context.(type) {
+	case Sourcer:
+		return fmt.Sprintf("%s:%d:%d", src.Source(), p.Line, p.Column)
+	default:
+		return fmt.Sprintf("Pos(offset=%d, line=%d, column=%d)", p.Offset, p.Line, p.Column)
+	}
 }
 
 type TokenMap struct {
@@ -49,7 +59,6 @@ func (m TokenMap) Type(tok string) Type {
 }
 
 func (m TokenMap) TokenString(tok *Token) string {
-	//TODO: refactor to print pos & token string properly
 	return fmt.Sprintf("%s(%d,%s)", m.Id(tok.Type), tok.Type, tok.Lit)
 }
 
@@ -57,6 +66,74 @@ func (m TokenMap) StringType(typ Type) string {
 	return fmt.Sprintf("%s(%d)", m.Id(typ), typ)
 }
 
+// Equals returns returns true if the token Type and Lit are matches.
+func (t *Token) Equals(rhs interface{}) bool {
+	switch rhsT := rhs.(type) {
+	case *Token:
+		return t == rhsT || (t.Type == rhsT.Type && bytes.Equal(t.Lit, rhsT.Lit))
+	default:
+		return false
+	}
+}
+
+// CharLiteralValue returns the string value of the char literal.
+func (t *Token) CharLiteralValue() string {
+	return string(t.Lit[1 : len(t.Lit)-1])
+}
+
+// Float32Value returns the float32 value of the token or an error if the token literal does not
+// denote a valid float32.
+func (t *Token) Float32Value() (float32, error) {
+	if v, err := strconv.ParseFloat(string(t.Lit), 32); err != nil {
+		return 0, err
+	} else {
+		return float32(v), nil
+	}
+}
+
+// Float64Value returns the float64 value of the token or an error if the token literal does not
+// denote a valid float64.
+func (t *Token) Float64Value() (float64, error) {
+	return strconv.ParseFloat(string(t.Lit), 64)
+}
+
+// IDValue returns the string representation of an identifier token.
+func (t *Token) IDValue() string {
+	return string(t.Lit)
+}
+
+// Int32Value returns the int32 value of the token or an error if the token literal does not
+// denote a valid float64.
+func (t *Token) Int32Value() (int32, error) {
+	if v, err := strconv.ParseInt(string(t.Lit), 10, 64); err != nil {
+		return 0, err
+	} else {
+		return int32(v), nil
+	}
+}
+
+// Int64Value returns the int64 value of the token or an error if the token literal does not
+// denote a valid float64.
+func (t *Token) Int64Value() (int64, error) {
+	return strconv.ParseInt(string(t.Lit), 10, 64)
+}
+
+// UTF8Rune decodes the UTF8 rune in the token literal. It returns utf8.RuneError if
+// the token literal contains an invalid rune.
+func (t *Token) UTF8Rune() (rune, error) {
+	r, _ := utf8.DecodeRune(t.Lit)
+	if r == utf8.RuneError {
+		err := fmt.Errorf("Invalid rune")
+		return r, err
+	}
+	return r, nil
+}
+
+// StringValue returns the string value of the token literal.
+func (t *Token) StringValue() string {
+	return string(t.Lit[1 : len(t.Lit)-1])
+}
+
 var TokMap = TokenMap{
 	typeMap: []string{
 		"INVALID",
diff --git a/internal/util/litconv.go b/internal/util/litconv.go
index 1aeb616..16407a4 100644
--- a/internal/util/litconv.go
+++ b/internal/util/litconv.go
@@ -9,11 +9,9 @@ import (
 	"unicode/utf8"
 )
 
-/* Interface */
+// Interface.
 
-/*
-Convert the literal value of a scanned token to rune
-*/
+// RuneValue will convert the literal value of a scanned token to a rune.
 func RuneValue(lit []byte) rune {
 	if lit[1] == '\\' {
 		return escapeCharVal(lit)
@@ -25,22 +23,17 @@ func RuneValue(lit []byte) rune {
 	return r
 }
 
-/*
-Convert the literal value of a scanned token to int64
-*/
+// UintValue will attempt to parse a byte-slice as a signed base-10 64-bit integer.
 func IntValue(lit []byte) (int64, error) {
 	return strconv.ParseInt(string(lit), 10, 64)
 }
 
-/*
-Convert the literal value of a scanned token to uint64
-*/
+// UintValue will attempt to parse a byte-slice as an unsigned base-10 64-bit integer.
 func UintValue(lit []byte) (uint64, error) {
 	return strconv.ParseUint(string(lit), 10, 64)
 }
 
-/* Util */
-
+// Helpers.
 func escapeCharVal(lit []byte) rune {
 	var i, base, max uint32
 	offset := 2
diff --git a/remove_test.go b/remove_test.go
index 9e28fcc..cf8248b 100644
--- a/remove_test.go
+++ b/remove_test.go
@@ -64,32 +64,25 @@ func TestRemoveNode(t *testing.T) {
 	}
 
 	expected := `digraph G {
-	Hello->World;
 	DevelopmentFunction:cluster_Development->CoreFunction:cluster_Core;
-	SupportingFunction:cluster_Supporting->CoreFunction:cluster_Core;
 	Hello->CoreFunction:cluster_Core;
+	Hello->World;
+	SupportingFunction:cluster_Supporting->CoreFunction:cluster_Core;
 	subgraph cluster_Core {
-	CoreFunction;
-
-}
-;
+		CoreFunction;
+	};
 	subgraph cluster_Development {
-	DevelopmentFunction;
-
-}
-;
+		DevelopmentFunction;
+	};
 	subgraph cluster_Supporting {
-	SupportingFunction;
-
-}
-;
+		SupportingFunction;
+	};
 	Hello;
 	World;
-
 }
 `
 
-	if g.String() != expected {
+	if got := g.String(); got != expected {
 		t.Fatalf("output is not expected")
 	}
 
@@ -98,29 +91,23 @@ func TestRemoveNode(t *testing.T) {
 	}
 
 	expected = `digraph G {
+	Hello->CoreFunction:cluster_Core;
 	Hello->World;
 	SupportingFunction:cluster_Supporting->CoreFunction:cluster_Core;
-	Hello->CoreFunction:cluster_Core;
 	subgraph cluster_Core {
-	CoreFunction;
-
-}
-;
+		CoreFunction;
+	};
 	subgraph cluster_Development {
 
-}
-;
+	};
 	subgraph cluster_Supporting {
-	SupportingFunction;
-
-}
-;
+		SupportingFunction;
+	};
 	Hello;
 	World;
-
 }
 `
-	if g.String() != expected {
+	if got := g.String(); got != expected {
 		t.Fatalf("output is not expected")
 	}
 
@@ -134,20 +121,14 @@ func TestRemoveNode(t *testing.T) {
 	expected = `digraph G {
 	SupportingFunction:cluster_Supporting->CoreFunction:cluster_Core;
 	subgraph cluster_Core {
-	CoreFunction;
-
-}
-;
+		CoreFunction;
+	};
 	subgraph cluster_Development {
 
-}
-;
+	};
 	subgraph cluster_Supporting {
-	SupportingFunction;
-
-}
-;
-
+		SupportingFunction;
+	};
 }
 `
 	if g.String() != expected {
@@ -160,19 +141,14 @@ func TestRemoveNode(t *testing.T) {
 
 	expected = `digraph G {
 	subgraph cluster_Core {
-	CoreFunction;
-
-}
-;
+		CoreFunction;
+	};
 	subgraph cluster_Development {
 
-}
-;
+	};
 	subgraph cluster_Supporting {
 
-}
-;
-
+	};
 }
 `
 
@@ -187,17 +163,13 @@ func TestRemoveNode(t *testing.T) {
 	expected = `digraph G {
 	subgraph cluster_Core {
 
-}
-;
+	};
 	subgraph cluster_Development {
 
-}
-;
+	};
 	subgraph cluster_Supporting {
 
-}
-;
-
+	};
 }
 `
 
@@ -264,39 +236,31 @@ func TestRemoveSubGraph(t *testing.T) {
 	}
 
 	expected := `digraph G {
-	Hello->World;
 	DevelopmentFunction:cluster_Development->CoreFunction:cluster_Core;
-	SupportingFunction:cluster_Supporting->CoreFunction:cluster_Core;
 	Hello->CoreFunction:cluster_Core;
+	Hello->World;
 	Hello->cluster_FooBar;
+	SupportingFunction:cluster_Supporting->CoreFunction:cluster_Core;
 	subgraph cluster_Core {
-	label=Core;
-	CoreFunction;
-
-}
-;
+		label=Core;
+		CoreFunction;
+	};
 	subgraph cluster_Development {
-	label=Development;
-	DevelopmentFunction;
-
-}
-;
+		label=Development;
+		DevelopmentFunction;
+	};
 	subgraph cluster_FooBar {
 
-}
-;
+	};
 	subgraph cluster_Supporting {
-	label=Supporting;
-	SupportingFunction;
-
-}
-;
+		label=Supporting;
+		SupportingFunction;
+	};
 	Hello;
 	World;
-
 }
 `
-	if g.String() != expected {
+	if got := g.String(); got != expected {
 		t.Fatalf("output is not expected")
 	}
 
@@ -305,32 +269,26 @@ func TestRemoveSubGraph(t *testing.T) {
 	}
 
 	expected = `digraph G {
-	Hello->World;
-	SupportingFunction:cluster_Supporting->CoreFunction:cluster_Core;
 	Hello->CoreFunction:cluster_Core;
+	Hello->World;
 	Hello->cluster_FooBar;
+	SupportingFunction:cluster_Supporting->CoreFunction:cluster_Core;
 	subgraph cluster_Core {
-	label=Core;
-	CoreFunction;
-
-}
-;
+		label=Core;
+		CoreFunction;
+	};
 	subgraph cluster_FooBar {
 
-}
-;
+	};
 	subgraph cluster_Supporting {
-	label=Supporting;
-	SupportingFunction;
-
-}
-;
+		label=Supporting;
+		SupportingFunction;
+	};
 	Hello;
 	World;
-
 }
 `
-	if g.String() != expected {
+	if got := g.String(); got != expected {
 		t.Fatalf("output is not expected")
 	}
 
@@ -339,25 +297,21 @@ func TestRemoveSubGraph(t *testing.T) {
 	}
 
 	expected = `digraph G {
-	Hello->World;
 	Hello->CoreFunction:cluster_Core;
+	Hello->World;
 	Hello->cluster_FooBar;
 	subgraph cluster_Core {
-	label=Core;
-	CoreFunction;
-
-}
-;
+		label=Core;
+		CoreFunction;
+	};
 	subgraph cluster_FooBar {
 
-}
-;
+	};
 	Hello;
 	World;
-
 }
 `
-	if g.String() != expected {
+	if got := g.String(); got != expected {
 		t.Fatalf("output is not expected")
 	}
 
@@ -370,11 +324,9 @@ func TestRemoveSubGraph(t *testing.T) {
 	Hello->cluster_FooBar;
 	subgraph cluster_FooBar {
 
-}
-;
+	};
 	Hello;
 	World;
-
 }
 `
 	if g.String() != expected {
@@ -389,7 +341,6 @@ func TestRemoveSubGraph(t *testing.T) {
 	Hello->World;
 	Hello;
 	World;
-
 }
 `
 	if g.String() != expected {
diff --git a/write.go b/write.go
index 2c18a5b..f3fb03b 100644
--- a/write.go
+++ b/write.go
@@ -125,7 +125,7 @@ func (w *writer) Write() (*ast.Graph, error) {
 
 	t.StmtList = appendAttrs(t.StmtList, w.Attrs)
 
-	for _, edge := range w.Edges.Edges {
+	for _, edge := range w.Edges.Sorted() {
 		e, err := w.newEdgeStmt(edge)
 		if err != nil {
 			return nil, err

Debdiff

[The following lists of changes regard files as different if they have different names, permissions or owners.]

Files in second set of .debs but not in first

-rw-r--r--  root/root   /usr/share/gocode/src/github.com/awalterschulze/gographviz/internal/parser/context.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/awalterschulze/gographviz/internal/token/context.go

No differences were encountered in the control files

More details

Full run details