pretty

package
v0.0.1 Latest Latest
Warning

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

Go to latest
Published: Mar 18, 2022 License: Apache-2.0 Imports: 2 Imported by: 0

Documentation

Overview

Package pretty prints documents based on a target line width.

See: https://homepages.inf.ed.ac.uk/wadler/papers/prettier/prettier.pdf

The paper describes a simple algorithm for printing a document tree with a single layout defined by text, newlines, and indentation. This concept is then expanded for documents that have more than one possible layout using a union type of two documents where both documents reduce to the same document, but one document has been flattened to a single line. A method ("best") is described that chooses the best of these two layouts (i.e., it removes all union types from a document tree). It works by tracking the desired and current line length and choosing either the flattened side of a union if it fits on the current line, or else the non-flattened side. The paper then describes various performance improvements that reduce the search space of the best function such that it can complete in O(n) instead of O(n^2) time, where n is the number of nodes.

For example code with SQL to experiment further, refer to https://github.com/knz/prettier/

Example (Align)

Example_align demonstrates alignment.

package main

import (
	"fmt"

	"github.com/labulakalia/sqlfmt/cockroach/pkg/util/pretty"
)

func main() {
	testData := []pretty.Doc{
		pretty.JoinGroupAligned("SELECT", ",",
			pretty.Text("aaa"),
			pretty.Text("bbb"),
			pretty.Text("ccc")),
		pretty.Table(pretty.TableRightAlignFirstColumn, pretty.Text,
			pretty.TableRow{Label: "SELECT",
				Doc: pretty.Join(",",
					pretty.Text("aaa"),
					pretty.Text("bbb"),
					pretty.Text("ccc")),
			},
			pretty.TableRow{Label: "FROM",
				Doc: pretty.Join(",",
					pretty.Text("t"),
					pretty.Text("u"),
					pretty.Text("v")),
			}),
		pretty.Table(pretty.TableLeftAlignFirstColumn, pretty.Text,
			pretty.TableRow{Label: "SELECT",
				Doc: pretty.Join(",",
					pretty.Text("aaa"),
					pretty.Text("bbb"),
					pretty.Text("ccc")),
			},
			pretty.TableRow{Label: "FROM",
				Doc: pretty.Join(",",
					pretty.Text("t"),
					pretty.Text("u"),
					pretty.Text("v")),
			}),
		pretty.Table(pretty.TableRightAlignFirstColumn, pretty.Text,
			pretty.TableRow{Label: "woo", Doc: nil}, // check nil rows are omitted
			pretty.TableRow{Label: "", Doc: pretty.Nil},
			pretty.TableRow{Label: "KEY", Doc: pretty.Text("VALUE")},
			pretty.TableRow{Label: "", Doc: pretty.Text("OTHERVALUE")},
			pretty.TableRow{Label: "AAA", Doc: pretty.Nil}, // check no extra space is added
		),
	}
	for _, n := range []int{1, 15, 30, 80} {
		fmt.Printf("%d:\n", n)
		for _, doc := range testData {
			p := pretty.Pretty(doc, n, true /*useTabs*/, 4 /*tabWidth*/, nil /*keywordTransform*/)
			fmt.Printf("%s\n\n", p)
		}
	}

}
Output:

1:
SELECT
	aaa,
	bbb,
	ccc

SELECT
	aaa,
	bbb,
	ccc
FROM
	t,
	u,
	v

SELECT
	aaa,
	bbb,
	ccc
FROM
	t,
	u,
	v

KEY
	VALUE
OTHERVALUE
AAA

15:
SELECT aaa,
       bbb,
       ccc

SELECT aaa,
       bbb,
       ccc
  FROM t, u, v

SELECT aaa,
       bbb,
       ccc
FROM   t, u, v

KEY VALUE
    OTHERVALUE
AAA

30:
SELECT aaa, bbb, ccc

SELECT aaa, bbb, ccc
  FROM t, u, v

SELECT aaa, bbb, ccc
FROM   t, u, v

KEY VALUE OTHERVALUE AAA

80:
SELECT aaa, bbb, ccc

SELECT aaa, bbb, ccc FROM t, u, v

SELECT aaa, bbb, ccc FROM t, u, v

KEY VALUE OTHERVALUE AAA
Example (Tree)

ExampleTree demonstrates the Tree example from the paper.

package main

import (
	"fmt"

	"github.com/labulakalia/sqlfmt/cockroach/pkg/util/pretty"
)

func main() {
	type Tree struct {
		s  string
		n  []Tree
		op string
	}
	tree := Tree{
		s: "aaa",
		n: []Tree{
			{
				s: "bbbbb",
				n: []Tree{
					{s: "ccc"},
					{s: "dd"},
					{s: "ee", op: "*", n: []Tree{
						{s: "some"},
						{s: "another", n: []Tree{{s: "2a"}, {s: "2b"}}},
						{s: "final"},
					}},
				},
			},
			{s: "eee"},
			{
				s: "ffff",
				n: []Tree{
					{s: "gg"},
					{s: "hhh"},
					{s: "ii"},
				},
			},
		},
	}
	var (
		showTree    func(Tree) pretty.Doc
		showTrees   func([]Tree) pretty.Doc
		showBracket func([]Tree) pretty.Doc
	)
	showTrees = func(ts []Tree) pretty.Doc {
		if len(ts) == 1 {
			return showTree(ts[0])
		}
		return pretty.Fold(pretty.Concat,
			showTree(ts[0]),
			pretty.Text(","),
			pretty.Line,
			showTrees(ts[1:]),
		)
	}
	showBracket = func(ts []Tree) pretty.Doc {
		if len(ts) == 0 {
			return pretty.Nil
		}
		return pretty.Fold(pretty.Concat,
			pretty.Text("["),
			pretty.NestT(showTrees(ts)),
			pretty.Text("]"),
		)
	}
	showTree = func(t Tree) pretty.Doc {
		var doc pretty.Doc
		if t.op != "" {
			var operands []pretty.Doc
			for _, o := range t.n {
				operands = append(operands, showTree(o))
			}
			doc = pretty.Fold(pretty.Concat,
				pretty.Text("("),
				pretty.JoinNestedRight(
					pretty.Text(t.op), operands...),
				pretty.Text(")"),
			)
		} else {
			doc = showBracket(t.n)
		}
		return pretty.Group(pretty.Concat(
			pretty.Text(t.s),
			pretty.NestS(int16(len(t.s)), doc),
		))
	}
	for _, n := range []int{1, 30, 80} {
		p := pretty.Pretty(showTree(tree), n, false /*useTabs*/, 4 /*tabWidth*/, nil /*keywordTransform*/)
		fmt.Printf("%d:\n%s\n\n", n, p)
	}
}
Output:

1:
aaa[bbbbb[ccc,
            dd,
            ee(some
              * another[2a,
                        2b]
              * final)],
    eee,
    ffff[gg,
            hhh,
            ii]]

30:
aaa[bbbbb[ccc,
            dd,
            ee(some
              * another[2a,
                        2b]
              * final)],
    eee,
    ffff[gg, hhh, ii]]

80:
aaa[bbbbb[ccc, dd, ee(some * another[2a, 2b] * final)], eee, ffff[gg, hhh, ii]]

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Pretty

func Pretty(d Doc, n int, useTabs bool, tabWidth int, keywordTransform func(string) string) string

Pretty returns a pretty-printed string for the Doc d at line length n and tab width t. Keyword Docs are filtered through keywordTransform if not nil. keywordTransform must not change the visible length of its argument. It can, for example, add invisible characters like control codes (colors, etc.).

Types

type Doc

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

Doc represents a document as described by the type "DOC" in the referenced paper. This is the abstract representation constructed by the pretty-printing code.

var HardLine Doc = hardline{}

HardLine is a newline and cannot be flattened.

var Line Doc = line{}

Line is a newline and is flattened to a space.

var Nil Doc = nilDoc{}

Nil is the NIL constructor.

var SoftBreak Doc = softbreak{}

SoftBreak is a newline and is flattened to an empty string.

func Align

func Align(d Doc) Doc

Align renders document d with the space-based nesting level set to the current column.

func AlignUnder

func AlignUnder(head, nested Doc) Doc

AlignUnder aligns nested to the right of head, and, if this does not fit on the line, nests nested under head.

func BracketDoc

func BracketDoc(l, x, r Doc) Doc

BracketDoc is like Bracket except it accepts Docs instead of strings.

func Concat

func Concat(a, b Doc) Doc

Concat is the <> constructor. This uses simplifyNil to avoid actually inserting NIL docs in the abstract tree.

func ConcatDoc

func ConcatDoc(a, b, between Doc) Doc

ConcatDoc concatenates two Docs with between.

func ConcatLine

func ConcatLine(a, b Doc) Doc

ConcatLine concatenates two Docs with a Line.

func ConcatSpace

func ConcatSpace(a, b Doc) Doc

ConcatSpace concatenates two Docs with a space.

func Fillwords

func Fillwords(d ...Doc) Doc

Fillwords fills lines with as many docs as will fit joined with a space or line.

func Fold

func Fold(f func(a, b Doc) Doc, d ...Doc) Doc

Fold applies f recursively to all Docs in d.

func FoldMap

func FoldMap(f func(a, b Doc) Doc, g func(Doc) Doc, d ...Doc) Doc

FoldMap applies f recursively to all Docs in d processed through g.

func Group

func Group(d Doc) Doc

Group will format d on one line if possible.

func Join

func Join(s string, d ...Doc) Doc

Join joins Docs d with string s and Line. There is no space between each item in d and the subsequent instance of s.

func JoinDoc

func JoinDoc(s Doc, d ...Doc) Doc

JoinDoc joins Docs d with Doc s.

func JoinGroupAligned

func JoinGroupAligned(head, divider string, d ...Doc) Doc

JoinGroupAligned nests joined d with divider under head.

func JoinNestedOuter

func JoinNestedOuter(lbl string, docFn func(string) Doc, d ...Doc) Doc

JoinNestedOuter attempts to de-indent the items after the first so that the operator appears in the right margin. This replacement is only done if there are enough "simple spaces" (as per previous uses of Align) to de-indent. No attempt is made to de-indent hard tabs, otherwise alignment may break on output devices with a different physical tab width. docFn should be set to Text or Keyword and will be used when converting lbl to a Doc.

func JoinNestedRight

func JoinNestedRight(sep Doc, nested ...Doc) Doc

JoinNestedRight nests nested with string s. Every item after the first is indented. For example: aaaa <sep> bbb

bbb

<sep> ccc

ccc

func Keyword

func Keyword(s string) Doc

Keyword is identical to Text except they are filtered by keywordTransform. The computed width is always len(s), regardless of the result of the result of the transform. This allows for things like coloring and other control characters in the output.

func NestS

func NestS(n int16, d Doc) Doc

NestS is the NESTS constructor.

func NestT

func NestT(d Doc) Doc

NestT is the NESTT constructor.

func NestUnder

func NestUnder(head, nested Doc) Doc

NestUnder nests nested under head.

func Stack

func Stack(d ...Doc) Doc

Stack concats Docs with a Line between each.

func Table

func Table(alignment TableAlignment, docFn func(string) Doc, rows ...TableRow) Doc

Table defines a document that formats a list of pairs of items either:

  • as a 2-column table, with the two columns aligned for example: SELECT aaa bbb FROM ccc
  • as sections, for example: SELECT aaa bbb FROM ccc

We restrict the left value in each list item to be a one-line string to make the width computation efficient.

For convenience, the function also skips over rows with a nil pointer as doc.

docFn should be set to Text or Keyword and will be used when converting TableRow label's to Docs.

func Text

func Text(s string) Doc

Text is the TEXT constructor.

type TableAlignment

type TableAlignment int

TableAlignment should be used as first argument to Table().

const (
	// TableNoAlign does not use alignment and instead uses NestUnder.
	TableNoAlign TableAlignment = iota
	// TableRightAlignFirstColumn right-aligns (left-pads) the first column.
	TableRightAlignFirstColumn
	// TableLeftAlignFirstColumn left-aligns (right-pads) the first column.
	TableLeftAlignFirstColumn
)

type TableRow

type TableRow struct {
	Label string
	Doc   Doc
}

TableRow is the data for one row of a RLTable (see below).

Jump to

Keyboard shortcuts

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