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 ¶
- func Pretty(d Doc, n int, useTabs bool, tabWidth int, keywordTransform func(string) string) string
- type Doc
- func Align(d Doc) Doc
- func AlignUnder(head, nested Doc) Doc
- func BracketDoc(l, x, r Doc) Doc
- func Concat(a, b Doc) Doc
- func ConcatDoc(a, b, between Doc) Doc
- func ConcatLine(a, b Doc) Doc
- func ConcatSpace(a, b Doc) Doc
- func Fillwords(d ...Doc) Doc
- func Fold(f func(a, b Doc) Doc, d ...Doc) Doc
- func FoldMap(f func(a, b Doc) Doc, g func(Doc) Doc, d ...Doc) Doc
- func Group(d Doc) Doc
- func Join(s string, d ...Doc) Doc
- func JoinDoc(s Doc, d ...Doc) Doc
- func JoinGroupAligned(head, divider string, d ...Doc) Doc
- func JoinNestedOuter(lbl string, docFn func(string) Doc, d ...Doc) Doc
- func JoinNestedRight(sep Doc, nested ...Doc) Doc
- func Keyword(s string) Doc
- func NestS(n int16, d Doc) Doc
- func NestT(d Doc) Doc
- func NestUnder(head, nested Doc) Doc
- func Stack(d ...Doc) Doc
- func Table(alignment TableAlignment, docFn func(string) Doc, rows ...TableRow) Doc
- func Text(s string) Doc
- type TableAlignment
- type TableRow
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Pretty ¶
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 AlignUnder ¶
AlignUnder aligns nested to the right of head, and, if this does not fit on the line, nests nested under head.
func BracketDoc ¶
BracketDoc is like Bracket except it accepts Docs instead of strings.
func Concat ¶
Concat is the <> constructor. This uses simplifyNil to avoid actually inserting NIL docs in the abstract tree.
func Join ¶
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 JoinGroupAligned ¶
JoinGroupAligned nests joined d with divider under head.
func JoinNestedOuter ¶
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 ¶
JoinNestedRight nests nested with string s. Every item after the first is indented. For example: aaaa <sep> bbb
bbb
<sep> ccc
ccc
func Keyword ¶
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 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.
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 )