dot

package module
v0.0.0-...-2fb2b97 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 27, 2026 License: GPL-3.0 Imports: 8 Imported by: 0

README

DOT

A toolchain for the DOT language. Includes dotx lsp for editor integration, dotx fmt for formatting, dotx watch for live preview and dotx inspect for examining syntax.

Install

go install github.com/teleivo/dot/cmd/dotx@latest

LSP

dotx lsp starts a Language Server Protocol server for DOT files.

Features:

  • Diagnostics (syntax errors as you type)
  • Formatting
  • Attribute completion with context-aware filtering (node, edge, graph attributes)
  • Hover documentation for attributes
  • Document symbols for outline view (graphs, subgraphs, nodes, edges)
  • Go to definition for node IDs
  • Find references for node IDs

Formatter

Format your DOT files with dotx fmt. dotx fmt is inspired by gofmt. As such it is opinionated and has no options to change its format.

Usage

Format a file in-place:

dotx fmt graph.dot

Format all .dot and .gv files in a directory (recursively):

dotx fmt ./graphs/

Format from stdin to stdout:

dotx fmt < input.dot > output.dot

Or try it directly:

dotx fmt <<EOF
digraph git{node[shape=rect]"22a1e48"->"abd0f59"->"e83ea81"[label="main"]"22a1e48"->"b4ec655"[label="lsp"]"b4ec655"->"c4a3242"->"e38f243"->"02314ea" "02314ea"->"6504ef3"[label="partial sync"]}
EOF
Design principles
  • No configuration: dotx fmt is opinionated and has no options to change its format.
  • Idempotency: Formatting the same code multiple times produces identical output.
  • Only formats valid code: Parse errors are reported to stderr and no output is produced. The formatter does not output partial or malformed results. When formatting a directory, all files are processed and errors from all files are reported (one error per file). Files without errors are formatted successfully even when other files have errors.
  • Comments are preserved as-is: Comment content is never modified - no line wrapping or whitespace normalization. Only indentation is adjusted to match surrounding code. This preserves ASCII art, code examples, and tables that may appear in comments.
  • Comment placement is normalized: Line comments (//, #) always force a line break after them. Single-line block comments (/* c1 */) stay inline with surrounding tokens. Multi-line block comments force a line break after the closing */.
Testing

dotx fmt uses two test strategies:

  • Idempotency tests verify formatting is stable
  • Visual tests ensure formatting preserves graph semantics by comparing dot -Tplain outputs

Run visual tests on external graphs:

# Sync samples from the Graphviz repository (https://gitlab.com/graphviz/graphviz)
./sync-graphviz-samples.sh

# Run from repository root
DOTX_TEST_DIR=../../samples-graphviz/tests go test -C cmd/dotx -v -run TestVisualOutput

# For comprehensive testing of all sample directories
./run-visual-tests.sh

Note: Some tests will fail due to known limitations such as HTML labels. These failures are expected and indicate features not yet supported rather than bugs.

Inspect

dotx inspect provides commands for examining DOT source code structure.

Tree

Print the concrete syntax tree (CST) representation:

echo 'digraph { a -> b }' | dotx inspect tree

Output:

File
	Graph
		'digraph'
		'{'
		StmtList
			EdgeStmt
				NodeID
					ID
						'a'
				'->'
				NodeID
					ID
						'b'
		'}'

Use -format=scheme for a scheme-like representation with positions.

Tokens

Print the token stream:

echo 'digraph { a -> b }' | dotx inspect tokens

Output:

POSITION   TYPE     LITERAL  ERROR
1:1-1:7    digraph  digraph
1:9        {        {
1:11       ID       a
1:13-1:14  ->       ->
1:16       ID       b
1:18       }        }

Watch

Preview DOT files as SVG in your browser with live reload.

Requires the dot executable from Graphviz.

dotx watch graph.dot

The file watcher is designed for editors that use atomic writes (rename temp file to target), such as Neovim and Vim. Editors that write files in multiple steps may cause brief flashes of errors or partial content.

Documentation

View the package documentation locally with an interactive example playground:

# Install pkgsite (Go's documentation server)
go install golang.org/x/pkgsite/cmd/pkgsite@latest

# Run the documentation server
pkgsite -open .

This opens a browser with pkg.go.dev-style documentation where you can:

  • Read the full package documentation
  • View and run the interactive example
  • Modify the example code (e.g., change NewDoc(40) to different column widths to see how the layout algorithm reflows text to fit within the specified maximum)

Neovim

Plugin

The nvim/ directory contains a Neovim plugin with commands:

  • :Dot inspect - visualize the CST in a split window with live updates and cursor tracking
  • :Dot watch - start dotx watch and open the browser for live SVG preview

Installation with lazy.nvim:

return {
  'teleivo/dot',
  ft = 'dot',
  opts = {},
}
LSP Configuration

Neovim 0.11+ with lsp/ directory (see :help lsp-config):

-- ~/.config/nvim/lsp/dotls.lua
return {
  cmd = { 'dotx', 'lsp' },
  filetypes = { 'dot' },
}
-- ~/.config/nvim/init.lua
vim.lsp.enable('dotls')

Limitations

  • The scanner assumes UTF-8 encoded input. Invalid UTF-8 byte sequences are replaced with the Unicode replacement character (U+FFFD) and reported as errors. Files in other encodings (UTF-16, Latin-1, etc.) must be converted to UTF-8 first.
  • The LSP server only supports UTF-8 position encoding. According to the LSP specification, servers must support UTF-16 as the default. However, dotx lsp always uses UTF-8 regardless of what the client offers. This works correctly with clients that support UTF-8 (such as Neovim) but may cause incorrect character positions with clients that only support UTF-16.
  • The formatter uses Unicode code points (runes) for measuring text width and line length. This does not account for grapheme clusters or display width, so characters like emojis (which may render as double-width) or combining characters will cause the formatter's column calculations to differ from visual appearance in editors.

The following are not supported as I do not need them:

Acknowledgments

The parser uses a homogeneous tree structure and practical error recovery techniques inspired by matklad's Resilient LL Parsing Tutorial. The full event-based two-phase parsing approach was too complex for a simple language like DOT.

The layout package is a Go port of allman by mcyoung. The layout algorithm and design are based on the excellent article "The Art of Formatting Code".

Disclaimer

I wrote this library for my personal projects and it is provided as-is without warranty. It is tailored to my needs and my intention is not to adjust it to someone else's liking. Feel free to use it!

See LICENSE for full license terms.

Documentation

Overview

Package dot provides a parser for the DOT language.

The parser implements an error-resilient recursive descent parser that produces a concrete syntax tree (CST) representation of DOT source code. It can parse syntactically invalid input and recover to continue parsing, collecting all errors encountered during parsing.

DOT Grammar

The parser implements the following grammar from the DOT language specification:

graph      : [ 'strict' ] ( 'graph' | 'digraph' ) [ ID ] '{' stmt_list '}'
stmt_list  : [ stmt [ ';' ] stmt_list ]
stmt       : node_stmt | edge_stmt | attr_stmt | ID '=' ID | subgraph
attr_stmt  : ( 'graph' | 'node' | 'edge' ) attr_list
attr_list  : '[' [ a_list ] ']' [ attr_list ]
a_list     : ID '=' ID [ ( ';' | ',' ) ] [ a_list ]
edge_stmt  : ( node_id | subgraph ) edgeRHS [ attr_list ]
edgeRHS    : edgeop ( node_id | subgraph ) [ edgeRHS ]
node_stmt  : node_id [ attr_list ]
node_id    : ID [ port ]
port       : ':' ID [ ':' compass_pt ] | ':' compass_pt
subgraph   : [ 'subgraph' [ ID ] ] '{' stmt_list '}'
compass_pt : 'n' | 'ne' | 'e' | 'se' | 's' | 'sw' | 'w' | 'nw' | 'c' | '_'

Where edgeop is '--' for undirected graphs and '->' for directed graphs.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func FirstID

func FirstID(tree *Tree) (token.Token, bool)

FirstID returns the token.ID of the first KindID child tree.

func TokenAt

func TokenAt(tree *Tree, want token.Kind, at int) (token.Token, bool)

TokenAt returns the child token at semantic index if it matches want. Comments are skipped.

func TokenFirst

func TokenFirst(tree *Tree, want token.Kind) (token.Token, bool)

TokenFirst returns the first child token matching want.

func TokenFirstWithin

func TokenFirstWithin(tree *Tree, want token.Kind, last int) (token.Token, int, bool)

TokenFirstWithin returns the first child token matching want within [0, last]. Comments are skipped. The returned index is the semantic index.

Types

type Child

type Child interface {
	// contains filtered or unexported methods
}

Child is a marker interface for tree node children. Implementations are TreeChild and TokenChild.

type Error

type Error struct {
	Pos token.Position
	Msg string
}

Error represents a parse error in DOT source code. The position Pos points to the beginning of the offending token, and the error condition is described by Msg.

func (Error) Error

func (e Error) Error() string

Error formats the error as "line:column: message".

type Format

type Format int

Format specifies the output representation for rendering a Tree.

const (
	// Default renders the formatted output as indented text.
	Default Format = iota
	// Scheme renders the tree as S-expressions with position annotations. Each node is rendered
	// as (NodeType (@ startLine startCol endLine endCol) children...) and tokens are rendered as
	// ('token' (@ startLine startCol endLine endCol)).
	Scheme
)

func NewFormat

func NewFormat(format string) (Format, error)

NewFormat converts a string to a Format constant. Valid values are "default" and "scheme". Returns an error if the format string is invalid.

type Parser

type Parser struct {
	// contains filtered or unexported fields
}

Parser parses DOT language source code into a concrete syntax tree.

Parser continues parsing after encountering errors, collecting all errors for later retrieval via Parser.Errors.

The parser uses one token of lookahead (LL(1)) and produces a Tree that preserves all tokens from the source.

func NewParser

func NewParser(src []byte) *Parser

NewParser creates a new parser that parses the given DOT source code.

func (*Parser) Errors

func (p *Parser) Errors() []Error

Errors returns all parse and scan errors collected during parsing.

func (*Parser) Parse

func (p *Parser) Parse() *Tree

Parse parses the DOT source code and returns the concrete syntax tree representation.

The returned Tree has type [File] and contains zero or more graphs. Parse always returns a tree, even when errors are encountered. Syntax errors are collected and can be retrieved via Parser.Errors.

type Scanner

type Scanner struct {
	// contains filtered or unexported fields
}

Scanner tokenizes DOT language source code into a stream of tokens.

func NewScanner

func NewScanner(src []byte) *Scanner

NewScanner creates a new scanner that tokenizes the given DOT source code.

func (*Scanner) Next

func (sc *Scanner) Next() token.Token

Next advances the scanners position by one token and returns it. When encountering invalid input, the scanner continues scanning. Invalid input results in a token of type token.ERROR with the error message in token.Token.Error that greedily consumes characters until a separator is encountered. A token of type token.EOF is returned once the end of input is reached.

type TokenChild

type TokenChild struct {
	token.Token
}

TokenChild wraps a token.Token as a child of a tree node.

type Tree

type Tree struct {
	Kind       TreeKind
	Children   []Child
	Start, End token.Position
}

Tree represents a node in the concrete syntax tree.

Type identifies the syntactic construct (e.g., [Graph], [NodeStmt], [ID]). Children contains the node's children in source order, which may be either TreeChild (subtrees) or TokenChild (tokens). Start and End mark the source positions.

func TreeAt

func TreeAt(tree *Tree, want TreeKind, at int) (*Tree, bool)

TreeAt returns the child tree at semantic index if it matches want. Comments are skipped.

func TreeFirst

func TreeFirst(tree *Tree, want TreeKind) (*Tree, bool)

TreeFirst returns the first child tree matching want.

func TreeFirstWithin

func TreeFirstWithin(tree *Tree, want TreeKind, last int) (*Tree, bool)

TreeFirstWithin returns the first child tree matching want within [0, last]. Comments are skipped.

func TreeLast

func TreeLast(tree *Tree, want TreeKind) (*Tree, bool)

TreeLast returns the last child tree matching want. Comments are skipped.

func (*Tree) Render

func (tree *Tree) Render(w io.Writer, format Format) error

Render writes the tree to w in the specified format. See Format for available formats.

func (*Tree) String

func (tree *Tree) String() string

String returns the tree formatted using the Default format.

type TreeChild

type TreeChild struct {
	*Tree
}

TreeChild wraps a Tree as a child of another tree node.

type TreeKind

type TreeKind uint32

TreeKind represents the type of syntax tree node (non-terminals).

const (
	KindErrorTree TreeKind = 1 << iota

	// Graph structure
	KindFile
	KindGraph
	KindSubgraph

	// Statements
	KindStmtList
	KindNodeStmt
	KindEdgeStmt
	KindAttrStmt

	// Node and Edge components
	KindNodeID
	KindPort
	KindCompassPoint

	// Attributes
	KindAttrList
	KindAList
	KindAttribute
	KindAttrName
	KindAttrValue

	KindID
)

func (TreeKind) String

func (tk TreeKind) String() string

String returns the name of the tree kind.

Directories

Path Synopsis
Package ast provides an abstract syntax tree representation for DOT graphs.
Package ast provides an abstract syntax tree representation for DOT graphs.
cmd
dotx command
internal
assert
Package assert provides runtime assertion checking for invariants.
Package assert provides runtime assertion checking for invariants.
format
Package format provides file and directory formatting for DOT files.
Package format provides file and directory formatting for DOT files.
layout
Package layout provides a declarative toolkit for building pretty printers and code formatters.
Package layout provides a declarative toolkit for building pretty printers and code formatters.
version
Package version provides build version information.
Package version provides build version information.
lsp
Package lsp implements a Language Server Protocol server for the DOT graph language.
Package lsp implements a Language Server Protocol server for the DOT graph language.
internal/attribute
Package attribute provides Graphviz attribute definitions and types.
Package attribute provides Graphviz attribute definitions and types.
internal/completion
Package completion provides autocompletion for DOT graph attributes.
Package completion provides autocompletion for DOT graph attributes.
internal/diagnostic
Package diagnostic provides parse error diagnostics for DOT graph files.
Package diagnostic provides parse error diagnostics for DOT graph files.
internal/hover
Package hover provides hover documentation for DOT graph elements.
Package hover provides hover documentation for DOT graph elements.
internal/navigate
Package navigate provides code navigation features for DOT graph documents.
Package navigate provides code navigation features for DOT graph documents.
internal/rpc
Package rpc implements JSON-RPC 2.0 message types for the Language Server Protocol.
Package rpc implements JSON-RPC 2.0 message types for the Language Server Protocol.
internal/tree
Package tree provides utilities for traversing DOT syntax trees.
Package tree provides utilities for traversing DOT syntax trees.
Package printer prints DOT syntax trees formatted in the spirit of [gofumpt].
Package printer prints DOT syntax trees formatted in the spirit of [gofumpt].
Package token defines constants representing the lexical tokens of the DOT language together with operations like printing, detecting Keywords or identifiers.
Package token defines constants representing the lexical tokens of the DOT language together with operations like printing, detecting Keywords or identifiers.
Package watch provides a file watcher that serves DOT graphs as SVG via HTTP.
Package watch provides a file watcher that serves DOT graphs as SVG via HTTP.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL