···11+// Copyright 2018 The CUE Authors
22+//
33+// Licensed under the Apache License, Version 2.0 (the "License");
44+// you may not use this file except in compliance with the License.
55+// You may obtain a copy of the License at
66+//
77+// http://www.apache.org/licenses/LICENSE-2.0
88+//
99+// Unless required by applicable law or agreed to in writing, software
1010+// distributed under the License is distributed on an "AS IS" BASIS,
1111+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212+// See the License for the specific language governing permissions and
1313+// limitations under the License.
1414+1515+// Package format implements standard formatting of CUE configurations.
1616+package format // import "cuelang.org/go/cue/format"
1717+1818+import (
1919+ "bytes"
2020+ "fmt"
2121+ "io"
2222+ "strings"
2323+ "text/tabwriter"
2424+2525+ "cuelang.org/go/cue/ast"
2626+ "cuelang.org/go/cue/parser"
2727+ "cuelang.org/go/cue/token"
2828+)
2929+3030+// An Option sets behavior of the formatter.
3131+type Option func(c *config)
3232+3333+// FileSet sets the file set that was used for creating a node. The formatter
3434+// generally formats fine without it.
3535+func FileSet(fset *token.FileSet) Option {
3636+ return func(c *config) { c.fset = fset }
3737+}
3838+3939+// Simplify allows the formatter to simplify output, such as removing
4040+// unnecessary quotes.
4141+func Simplify() Option {
4242+ return func(c *config) { c.simplify = true }
4343+}
4444+4545+// TODO: other options:
4646+//
4747+// const (
4848+// RawFormat Mode = 1 << iota // do not use a tabwriter; if set, UseSpaces is ignored
4949+// TabIndent // use tabs for indentation independent of UseSpaces
5050+// UseSpaces // use spaces instead of tabs for alignment
5151+// SourcePos // emit //line comments to preserve original source positions
5252+// )
5353+5454+// Node formats node in canonical cue fmt style and writes the result to dst.
5555+//
5656+// The node type must be *ast.File, []syntax.Decl, syntax.Expr, syntax.Decl, or
5757+// syntax.Spec. Node does not modify node. Imports are not sorted for nodes
5858+// representing partial source files (for instance, if the node is not an
5959+// *ast.File).
6060+//
6161+// The function may return early (before the entire result is written) and
6262+// return a formatting error, for instance due to an incorrect AST.
6363+//
6464+func Node(dst io.Writer, node ast.Node, opt ...Option) error {
6565+ cfg := newConfig(opt)
6666+ return cfg.fprint(dst, node)
6767+}
6868+6969+// Source formats src in canonical cue fmt style and returns the result or an
7070+// (I/O or syntax) error. src is expected to be a syntactically correct CUE
7171+// source file, or a list of CUE declarations or statements.
7272+//
7373+// If src is a partial source file, the leading and trailing space of src is
7474+// applied to the result (such that it has the same leading and trailing space
7575+// as src), and the result is indented by the same amount as the first line of
7676+// src containing code. Imports are not sorted for partial source files.
7777+//
7878+// Caution: Tools relying on consistent formatting based on the installed
7979+// version of cue (for instance, such as for presubmit checks) should execute
8080+// that cue binary instead of calling Source.
8181+//
8282+func Source(b []byte, opt ...Option) ([]byte, error) {
8383+ cfg := newConfig(opt)
8484+ if cfg.fset == nil {
8585+ cfg.fset = token.NewFileSet()
8686+ }
8787+8888+ f, err := parser.ParseFile(cfg.fset, "", b,
8989+ parser.ParseComments, parser.ParseLambdas)
9090+ if err != nil {
9191+ return nil, fmt.Errorf("parse: %s", err)
9292+ }
9393+9494+ // print AST
9595+ var buf bytes.Buffer
9696+ if err := cfg.fprint(&buf, f); err != nil {
9797+ return nil, fmt.Errorf("print: %s", err)
9898+ }
9999+ return buf.Bytes(), nil
100100+}
101101+102102+type config struct {
103103+ fset *token.FileSet
104104+105105+ UseSpaces bool
106106+ TabIndent bool
107107+ Tabwidth int // default: 8
108108+ Indent int // default: 0 (all code is indented at least by this much)
109109+110110+ simplify bool
111111+}
112112+113113+func newConfig(opt []Option) *config {
114114+ cfg := &config{Tabwidth: 8, TabIndent: true, UseSpaces: true}
115115+ for _, o := range opt {
116116+ o(cfg)
117117+ }
118118+ return cfg
119119+}
120120+121121+// Config defines the output of Fprint.
122122+func (cfg *config) fprint(output io.Writer, node interface{}) (err error) {
123123+ // print node
124124+ var p printer
125125+ p.init(cfg)
126126+ if err = printNode(node, &p); err != nil {
127127+ return err
128128+ }
129129+130130+ minwidth := cfg.Tabwidth
131131+132132+ padchar := byte('\t')
133133+ if cfg.UseSpaces {
134134+ padchar = ' '
135135+ }
136136+137137+ twmode := tabwriter.DiscardEmptyColumns | tabwriter.StripEscape
138138+ if cfg.TabIndent {
139139+ minwidth = 0
140140+ twmode |= tabwriter.TabIndent
141141+ }
142142+143143+ tw := tabwriter.NewWriter(output, minwidth, cfg.Tabwidth, 1, padchar, twmode)
144144+145145+ // write printer result via tabwriter/trimmer to output
146146+ if _, err = tw.Write(p.output); err != nil {
147147+ return
148148+ }
149149+150150+ err = tw.Flush()
151151+ return
152152+}
153153+154154+// A formatter walks a syntax.Node, interspersed with comments and spacing
155155+// directives, in the order that they would occur in printed form.
156156+type formatter struct {
157157+ *printer
158158+159159+ stack []frame
160160+ current frame
161161+ nestExpr int
162162+163163+ labelBuf []ast.Label
164164+}
165165+166166+func newFormatter(p *printer) *formatter {
167167+ f := &formatter{
168168+ printer: p,
169169+ current: frame{
170170+ settings: settings{
171171+ nodeSep: newline,
172172+ parentSep: newline,
173173+ },
174174+ },
175175+ }
176176+ return f
177177+}
178178+179179+type whiteSpace int
180180+181181+const (
182182+ ignore whiteSpace = 0
183183+184184+ // write a space, or disallow it
185185+ blank whiteSpace = 1 << iota
186186+ vtab // column marker
187187+ noblank
188188+189189+ nooverride
190190+191191+ comma // print a comma, unless trailcomma overrides it
192192+ trailcomma // print a trailing comma unless closed on same line
193193+ declcomma // write a comma when not at the end of line
194194+195195+ newline // write a line in a table
196196+ formfeed // next line is not part of the table
197197+ newsection // add two newlines
198198+199199+ indent // request indent an extra level after the next newline
200200+ unindent // unindent a level after the next newline
201201+ indented // element was indented.
202202+)
203203+204204+type frame struct {
205205+ cg []*ast.CommentGroup
206206+ pos int8
207207+208208+ settings
209209+ commentNewline bool
210210+}
211211+212212+type settings struct {
213213+ // separator is blank if the current node spans a single line and newline
214214+ // otherwise.
215215+ nodeSep whiteSpace
216216+ parentSep whiteSpace
217217+ override whiteSpace
218218+}
219219+220220+func (f *formatter) print(a ...interface{}) {
221221+ for _, x := range a {
222222+ f.Print(x)
223223+ switch x.(type) {
224224+ case string, token.Token: // , *syntax.BasicLit, *syntax.Ident:
225225+ f.current.pos++
226226+ }
227227+ }
228228+ f.visitComments(f.current.pos)
229229+}
230230+231231+func (f *formatter) formfeed() whiteSpace {
232232+ if f.current.nodeSep == blank {
233233+ return blank
234234+ }
235235+ return formfeed
236236+}
237237+238238+func (f *formatter) wsOverride(def whiteSpace) whiteSpace {
239239+ if f.current.override == ignore {
240240+ return def
241241+ }
242242+ return f.current.override
243243+}
244244+245245+func (f *formatter) onOneLine(node ast.Node) bool {
246246+ a := node.Pos()
247247+ b := node.End()
248248+ if f.fset != nil && a.IsValid() && b.IsValid() {
249249+ return f.lineFor(a) == f.lineFor(b)
250250+ }
251251+ // TODO: walk and look at relative positions to determine the same?
252252+ return false
253253+}
254254+255255+func (f *formatter) before(node ast.Node) bool {
256256+ f.stack = append(f.stack, f.current)
257257+ f.current = frame{settings: f.current.settings}
258258+ f.current.parentSep = f.current.nodeSep
259259+260260+ if node != nil {
261261+ if f.current.nodeSep != blank && f.onOneLine(node) {
262262+ f.current.nodeSep = blank
263263+ }
264264+ f.current.cg = node.Comments()
265265+ f.visitComments(f.current.pos)
266266+ return true
267267+ }
268268+ return false
269269+}
270270+271271+func (f *formatter) after(node ast.Node) {
272272+ f.visitComments(127)
273273+ p := len(f.stack) - 1
274274+ f.current = f.stack[p]
275275+ f.stack = f.stack[:p]
276276+ f.current.pos++
277277+ f.visitComments(f.current.pos)
278278+}
279279+280280+func (f *formatter) visitComments(until int8) {
281281+ c := &f.current
282282+283283+ for ; len(c.cg) > 0 && c.cg[0].Position <= until; c.cg = c.cg[1:] {
284284+ f.Print(c.cg[0])
285285+286286+ printBlank := false
287287+ if c.cg[0].Doc {
288288+ f.Print(newline)
289289+ printBlank = true
290290+ }
291291+ for _, c := range c.cg[0].List {
292292+ if !printBlank {
293293+ f.Print(vtab)
294294+ }
295295+ f.Print(c.Slash)
296296+ f.Print(c)
297297+ if strings.HasPrefix(c.Text, "//") {
298298+ f.Print(newline)
299299+ }
300300+ }
301301+ }
302302+}
+415
cue/format/format_test.go
···11+// Copyright 2018 The CUE Authors
22+//
33+// Licensed under the Apache License, Version 2.0 (the "License");
44+// you may not use this file except in compliance with the License.
55+// You may obtain a copy of the License at
66+//
77+// http://www.apache.org/licenses/LICENSE-2.0
88+//
99+// Unless required by applicable law or agreed to in writing, software
1010+// distributed under the License is distributed on an "AS IS" BASIS,
1111+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212+// See the License for the specific language governing permissions and
1313+// limitations under the License.
1414+1515+package format
1616+1717+// TODO: port more of the tests of go/printer
1818+1919+import (
2020+ "bytes"
2121+ "errors"
2222+ "flag"
2323+ "fmt"
2424+ "io"
2525+ "io/ioutil"
2626+ "path/filepath"
2727+ "testing"
2828+ "time"
2929+3030+ "cuelang.org/go/cue/ast"
3131+ "cuelang.org/go/cue/parser"
3232+ "cuelang.org/go/cue/token"
3333+)
3434+3535+var (
3636+ defaultConfig = newConfig([]Option{FileSet(fset)})
3737+ Fprint = defaultConfig.fprint
3838+)
3939+4040+const (
4141+ dataDir = "testdata"
4242+ tabwidth = 8
4343+)
4444+4545+var update = flag.Bool("update", false, "update golden files")
4646+4747+var fset = token.NewFileSet()
4848+4949+type checkMode uint
5050+5151+const (
5252+ export checkMode = 1 << iota
5353+ rawFormat
5454+ idempotent
5555+ simplify
5656+)
5757+5858+// format parses src, prints the corresponding AST, verifies the resulting
5959+// src is syntactically correct, and returns the resulting src or an error
6060+// if any.
6161+func format(src []byte, mode checkMode) ([]byte, error) {
6262+ // parse src
6363+ var opts []Option
6464+ if mode&simplify != 0 {
6565+ opts = append(opts, Simplify())
6666+ }
6767+6868+ res, err := Source(src, opts...)
6969+ if err != nil {
7070+ return nil, err
7171+ }
7272+7373+ // make sure formatted output is syntactically correct
7474+ if _, err := parser.ParseFile(fset, "", res,
7575+ parser.ParseLambdas, parser.AllErrors); err != nil {
7676+ return nil, fmt.Errorf("re-parse: %s\n%s", err, res)
7777+ }
7878+7979+ return res, nil
8080+}
8181+8282+// lineAt returns the line in text starting at offset offs.
8383+func lineAt(text []byte, offs int) []byte {
8484+ i := offs
8585+ for i < len(text) && text[i] != '\n' {
8686+ i++
8787+ }
8888+ return text[offs:i]
8989+}
9090+9191+// diff compares a and b.
9292+func diff(aname, bname string, a, b []byte) error {
9393+ var buf bytes.Buffer // holding long error message
9494+9595+ // compare lengths
9696+ if len(a) != len(b) {
9797+ fmt.Fprintf(&buf, "\nlength changed: len(%s) = %d, len(%s) = %d", aname, len(a), bname, len(b))
9898+ }
9999+100100+ // compare contents
101101+ line := 1
102102+ offs := 1
103103+ for i := 0; i < len(a) && i < len(b); i++ {
104104+ ch := a[i]
105105+ if ch != b[i] {
106106+ fmt.Fprintf(&buf, "\n%s:%d:%d: %s", aname, line, i-offs+1, lineAt(a, offs))
107107+ fmt.Fprintf(&buf, "\n%s:%d:%d: %s", bname, line, i-offs+1, lineAt(b, offs))
108108+ fmt.Fprintf(&buf, "\n\n")
109109+ break
110110+ }
111111+ if ch == '\n' {
112112+ line++
113113+ offs = i + 1
114114+ }
115115+ }
116116+117117+ if buf.Len() > 0 {
118118+ return errors.New(buf.String())
119119+ }
120120+ return nil
121121+}
122122+123123+func runcheck(t *testing.T, source, golden string, mode checkMode) {
124124+ src, err := ioutil.ReadFile(source)
125125+ if err != nil {
126126+ t.Error(err)
127127+ return
128128+ }
129129+130130+ res, err := format(src, mode)
131131+ if err != nil {
132132+ t.Error(err)
133133+ return
134134+ }
135135+136136+ // update golden files if necessary
137137+ if *update {
138138+ if err := ioutil.WriteFile(golden, res, 0644); err != nil {
139139+ t.Error(err)
140140+ }
141141+ return
142142+ }
143143+144144+ // get golden
145145+ gld, err := ioutil.ReadFile(golden)
146146+ if err != nil {
147147+ t.Error(err)
148148+ return
149149+ }
150150+151151+ // formatted source and golden must be the same
152152+ if err := diff(source, golden, res, gld); err != nil {
153153+ t.Error(err)
154154+ return
155155+ }
156156+157157+ if mode&idempotent != 0 {
158158+ // formatting golden must be idempotent
159159+ // (This is very difficult to achieve in general and for now
160160+ // it is only checked for files explicitly marked as such.)
161161+ res, err = format(gld, mode)
162162+ if err := diff(golden, fmt.Sprintf("format(%s)", golden), gld, res); err != nil {
163163+ t.Errorf("golden is not idempotent: %s", err)
164164+ }
165165+ }
166166+}
167167+168168+func check(t *testing.T, source, golden string, mode checkMode) {
169169+ // run the test
170170+ cc := make(chan int)
171171+ go func() {
172172+ runcheck(t, source, golden, mode)
173173+ cc <- 0
174174+ }()
175175+176176+ // wait with timeout
177177+ select {
178178+ case <-time.After(100000 * time.Second): // plenty of a safety margin, even for very slow machines
179179+ // test running past time out
180180+ t.Errorf("%s: running too slowly", source)
181181+ case <-cc:
182182+ // test finished within allotted time margin
183183+ }
184184+}
185185+186186+type entry struct {
187187+ source, golden string
188188+ mode checkMode
189189+}
190190+191191+// Use go test -update to create/update the respective golden files.
192192+var data = []entry{
193193+ {"comments.input", "comments.golden", 0},
194194+ {"simplify.input", "simplify.golden", simplify},
195195+ {"expressions.input", "expressions.golden", 0},
196196+}
197197+198198+func TestFiles(t *testing.T) {
199199+ t.Parallel()
200200+ for _, e := range data {
201201+ source := filepath.Join(dataDir, e.source)
202202+ golden := filepath.Join(dataDir, e.golden)
203203+ mode := e.mode
204204+ t.Run(e.source, func(t *testing.T) {
205205+ t.Parallel()
206206+ check(t, source, golden, mode)
207207+ // TODO(gri) check that golden is idempotent
208208+ //check(t, golden, golden, e.mode)
209209+ })
210210+ }
211211+}
212212+213213+// Verify that the printer can be invoked during initialization.
214214+func init() {
215215+ const name = "foobar"
216216+ var buf bytes.Buffer
217217+ if err := Fprint(&buf, &ast.Ident{Name: name}); err != nil {
218218+ panic(err) // error in test
219219+ }
220220+ // in debug mode, the result contains additional information;
221221+ // ignore it
222222+ if s := buf.String(); !debug && s != name {
223223+ panic("got " + s + ", want " + name)
224224+ }
225225+}
226226+227227+// Verify that the printer doesn't crash if the AST contains BadXXX nodes.
228228+func TestBadNodes(t *testing.T) {
229229+ const src = "package p\n("
230230+ const res = "package p\n\n(BadExpr)\n"
231231+ f, err := parser.ParseFile(fset, "", src, parser.ParseComments)
232232+ if err == nil {
233233+ t.Error("expected illegal program") // error in test
234234+ }
235235+ var buf bytes.Buffer
236236+ Fprint(&buf, f)
237237+ if buf.String() != res {
238238+ t.Errorf("got %q, expected %q", buf.String(), res)
239239+ }
240240+}
241241+242242+// idents is an iterator that returns all idents in f via the result channel.
243243+func idents(f *ast.File) <-chan *ast.Ident {
244244+ v := make(chan *ast.Ident)
245245+ go func() {
246246+ ast.Walk(f, func(n ast.Node) bool {
247247+ if ident, ok := n.(*ast.Ident); ok {
248248+ v <- ident
249249+ }
250250+ return true
251251+ }, nil)
252252+ close(v)
253253+ }()
254254+ return v
255255+}
256256+257257+// identCount returns the number of identifiers found in f.
258258+func identCount(f *ast.File) int {
259259+ n := 0
260260+ for range idents(f) {
261261+ n++
262262+ }
263263+ return n
264264+}
265265+266266+// Verify that the SourcePos mode emits correct //line comments
267267+// by testing that position information for matching identifiers
268268+// is maintained.
269269+func TestSourcePos(t *testing.T) {
270270+ const src = `package p
271271+272272+import (
273273+ "go/printer"
274274+ "math"
275275+ "regexp"
276276+)
277277+278278+pi = 3.14 // TODO: allow on same line
279279+xx = 0
280280+t: {
281281+ x: int
282282+ y: int
283283+ z: int
284284+ u: number
285285+ v: number
286286+ w: number
287287+}
288288+e: a*t.x + b*t.y
289289+290290+// two extra lines here // ...
291291+e2: c*t.z
292292+`
293293+294294+ // parse original
295295+ f1, err := parser.ParseFile(fset, "src", src, parser.ParseComments)
296296+ if err != nil {
297297+ t.Fatal(err)
298298+ }
299299+300300+ // pretty-print original
301301+ var buf bytes.Buffer
302302+ err = (&config{UseSpaces: true, Tabwidth: 8}).fprint(&buf, f1)
303303+ if err != nil {
304304+ t.Fatal(err)
305305+ }
306306+307307+ // parse pretty printed original
308308+ // (//line comments must be interpreted even w/o syntax.ParseComments set)
309309+ f2, err := parser.ParseFile(fset, "", buf.Bytes(),
310310+ parser.AllErrors, parser.ParseLambdas, parser.ParseComments)
311311+ if err != nil {
312312+ t.Fatalf("%s\n%s", err, buf.Bytes())
313313+ }
314314+315315+ // At this point the position information of identifiers in f2 should
316316+ // match the position information of corresponding identifiers in f1.
317317+318318+ // number of identifiers must be > 0 (test should run) and must match
319319+ n1 := identCount(f1)
320320+ n2 := identCount(f2)
321321+ if n1 == 0 {
322322+ t.Fatal("got no idents")
323323+ }
324324+ if n2 != n1 {
325325+ t.Errorf("got %d idents; want %d", n2, n1)
326326+ }
327327+328328+ // verify that all identifiers have correct line information
329329+ i2range := idents(f2)
330330+ for i1 := range idents(f1) {
331331+ i2 := <-i2range
332332+333333+ if i2 == nil || i1 == nil {
334334+ t.Fatal("non nil identifiers")
335335+ }
336336+ if i2.Name != i1.Name {
337337+ t.Errorf("got ident %s; want %s", i2.Name, i1.Name)
338338+ }
339339+340340+ l1 := fset.Position(i1.Pos()).Line
341341+ l2 := fset.Position(i2.Pos()).Line
342342+ if l2 != l1 {
343343+ t.Errorf("got line %d; want %d for %s", l2, l1, i1.Name)
344344+ }
345345+ }
346346+347347+ if t.Failed() {
348348+ t.Logf("\n%s", buf.Bytes())
349349+ }
350350+}
351351+352352+var decls = []string{
353353+ `import "fmt"`,
354354+ "pi = 3.1415\ne = 2.71828\n\nx = pi",
355355+}
356356+357357+func TestDeclLists(t *testing.T) {
358358+ for _, src := range decls {
359359+ file, err := parser.ParseFile(fset, "", "package p\n"+src, parser.ParseComments)
360360+ if err != nil {
361361+ panic(err) // error in test
362362+ }
363363+364364+ var buf bytes.Buffer
365365+ err = Fprint(&buf, file.Decls) // only print declarations
366366+ if err != nil {
367367+ panic(err) // error in test
368368+ }
369369+370370+ out := buf.String()
371371+372372+ if out != src {
373373+ t.Errorf("\ngot : %q\nwant: %q\n", out, src)
374374+ }
375375+ }
376376+}
377377+378378+var stmts = []string{
379379+ "i := 0",
380380+ "select {}\nvar a, b = 1, 2\nreturn a + b",
381381+ "go f()\ndefer func() {}()",
382382+}
383383+384384+type limitWriter struct {
385385+ remaining int
386386+ errCount int
387387+}
388388+389389+func (l *limitWriter) Write(buf []byte) (n int, err error) {
390390+ n = len(buf)
391391+ if n >= l.remaining {
392392+ n = l.remaining
393393+ err = io.EOF
394394+ l.errCount++
395395+ }
396396+ l.remaining -= n
397397+ return n, err
398398+}
399399+400400+// TextX is a skeleton test that can be filled in for debugging one-off cases.
401401+// Do not remove.
402402+func TestX(t *testing.T) {
403403+ const src = `
404404+ { e: k <-
405405+ for a, v in s}
406406+ a: b
407407+408408+`
409409+ b, err := format([]byte(src), 0)
410410+ if err != nil {
411411+ t.Error(err)
412412+ }
413413+ _ = b
414414+ // t.Error("\n", string(b))
415415+}
+688
cue/format/node.go
···11+// Copyright 2018 The CUE Authors
22+//
33+// Licensed under the Apache License, Version 2.0 (the "License");
44+// you may not use this file except in compliance with the License.
55+// You may obtain a copy of the License at
66+//
77+// http://www.apache.org/licenses/LICENSE-2.0
88+//
99+// Unless required by applicable law or agreed to in writing, software
1010+// distributed under the License is distributed on an "AS IS" BASIS,
1111+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212+// See the License for the specific language governing permissions and
1313+// limitations under the License.
1414+1515+package format
1616+1717+import (
1818+ "fmt"
1919+ "strconv"
2020+ "strings"
2121+2222+ "cuelang.org/go/cue/ast"
2323+ "cuelang.org/go/cue/parser"
2424+ "cuelang.org/go/cue/token"
2525+)
2626+2727+func printNode(node interface{}, f *printer) error {
2828+ s := newFormatter(f)
2929+3030+ // format node
3131+ f.allowed = nooverride // gobble initial whitespace.
3232+ switch x := node.(type) {
3333+ case *ast.File:
3434+ s.file(x)
3535+ case ast.Expr:
3636+ s.expr(x)
3737+ case ast.Decl:
3838+ s.decl(x)
3939+ // case ast.Node: // TODO: do we need this?
4040+ // s.walk(x)
4141+ case []ast.Decl:
4242+ s.walkDeclList(x)
4343+ default:
4444+ goto unsupported
4545+ }
4646+4747+ return nil
4848+4949+unsupported:
5050+ return fmt.Errorf("cue/format: unsupported node type %T", node)
5151+}
5252+5353+// Helper functions for common node lists. They may be empty.
5454+5555+func (f *formatter) walkDeclList(list []ast.Decl) {
5656+ f.before(nil)
5757+ for i, x := range list {
5858+ if i > 0 {
5959+ f.print(declcomma)
6060+ }
6161+ f.decl(x)
6262+ f.print(f.current.parentSep)
6363+ }
6464+ f.after(nil)
6565+}
6666+6767+func (f *formatter) walkSpecList(list []*ast.ImportSpec) {
6868+ f.before(nil)
6969+ for _, x := range list {
7070+ f.importSpec(x)
7171+ }
7272+ f.after(nil)
7373+}
7474+7575+func (f *formatter) walkClauseList(list []ast.Clause) {
7676+ f.before(nil)
7777+ for _, x := range list {
7878+ f.clause(x)
7979+ }
8080+ f.after(nil)
8181+}
8282+8383+func (f *formatter) walkExprList(list []ast.Expr, depth int) {
8484+ f.before(nil)
8585+ for _, x := range list {
8686+ f.before(x)
8787+ f.exprRaw(x, token.LowestPrec, depth)
8888+ f.print(comma, blank)
8989+ f.after(x)
9090+ }
9191+ f.after(nil)
9292+}
9393+9494+func (f *formatter) file(file *ast.File) {
9595+ f.before(file)
9696+ if file.Name != nil {
9797+ f.print(file.Package, "package")
9898+ f.print(blank, file.Name, newline, newsection)
9999+ }
100100+ f.current.pos = 3
101101+ f.visitComments(3)
102102+ f.walkDeclList(file.Decls)
103103+ f.after(file)
104104+ f.print(token.EOF)
105105+}
106106+func (f *formatter) decl(decl ast.Decl) {
107107+ if decl == nil {
108108+ return
109109+ }
110110+ if !f.before(decl) {
111111+ goto after
112112+ }
113113+ switch n := decl.(type) {
114114+ case *ast.Field:
115115+ // shortcut single-element structs.
116116+ lastSize := len(f.labelBuf)
117117+ f.labelBuf = f.labelBuf[:0]
118118+ first := n.Label
119119+ for {
120120+ obj, ok := n.Value.(*ast.StructLit)
121121+ if !ok || len(obj.Elts) != 1 || (obj.Lbrace.IsValid() && !f.printer.cfg.simplify) {
122122+ break
123123+ }
124124+125125+ // Verify that struct doesn't have inside comments and that
126126+ // element doesn't have doc comments.
127127+ hasComments := len(obj.Elts[0].Comments()) > 0
128128+ for _, c := range obj.Comments() {
129129+ if c.Position == 1 || c.Position == 2 {
130130+ hasComments = true
131131+ }
132132+ }
133133+ if hasComments {
134134+ break
135135+ }
136136+137137+ mem, ok := obj.Elts[0].(*ast.Field)
138138+ if !ok {
139139+ break
140140+ }
141141+ f.labelBuf = append(f.labelBuf, mem.Label)
142142+ n = mem
143143+ }
144144+145145+ if lastSize != len(f.labelBuf) {
146146+ f.print(formfeed)
147147+ }
148148+149149+ f.before(nil)
150150+ f.label(first)
151151+ for _, x := range f.labelBuf {
152152+ f.print(blank, nooverride)
153153+ f.label(x)
154154+ }
155155+ f.after(nil)
156156+157157+ nextFF := f.nextNeedsFormfeed(n.Value)
158158+ tab := vtab
159159+ if nextFF || f.nestExpr >= 1 {
160160+ tab = blank
161161+ }
162162+163163+ f.print(n.Colon, token.COLON, tab)
164164+ if n.Value != nil {
165165+ f.expr(n.Value)
166166+ } else {
167167+ f.current.pos++
168168+ f.visitComments(f.current.pos)
169169+ }
170170+171171+ if nextFF {
172172+ f.print(formfeed)
173173+ }
174174+175175+ case *ast.ComprehensionDecl:
176176+ f.decl(n.Field)
177177+ f.print(blank)
178178+ if n.Select != token.NoPos {
179179+ f.print(n.Select, token.ARROW, blank)
180180+ }
181181+ f.print(indent)
182182+ f.walkClauseList(n.Clauses)
183183+ f.print(unindent)
184184+ f.print("") // force whitespace to be written
185185+186186+ case *ast.BadDecl:
187187+ f.print(n.From, "*bad decl*", declcomma)
188188+189189+ case *ast.ImportDecl:
190190+ f.print(n.Import, "import")
191191+ if len(n.Specs) == 0 {
192192+ f.print(blank, n.Lparen, token.LPAREN, n.Rparen, token.RPAREN, newline)
193193+ }
194194+ switch {
195195+ case len(n.Specs) == 1:
196196+ if !n.Lparen.IsValid() {
197197+ f.print(blank)
198198+ f.walkSpecList(n.Specs)
199199+ break
200200+ }
201201+ fallthrough
202202+ default:
203203+ f.print(blank, n.Lparen, token.LPAREN, newline, indent)
204204+ f.walkSpecList(n.Specs)
205205+ f.print(unindent, newline, n.Rparen, token.RPAREN, newline)
206206+ }
207207+ f.print(newsection)
208208+209209+ case *ast.EmitDecl:
210210+ f.expr(n.Expr)
211211+ f.print(newline, newsection) // force newline
212212+213213+ case *ast.Alias:
214214+ f.expr(n.Ident)
215215+ f.print(blank, n.Equal, token.BIND, blank)
216216+ f.expr(n.Expr)
217217+ f.print(declcomma, newline) // implied
218218+ }
219219+after:
220220+ f.after(decl)
221221+}
222222+223223+func (f *formatter) nextNeedsFormfeed(n ast.Expr) bool {
224224+ switch x := n.(type) {
225225+ case *ast.StructLit:
226226+ return true
227227+ case *ast.BasicLit:
228228+ return strings.IndexByte(x.Value, '\n') >= 0
229229+ case *ast.ListLit:
230230+ return true
231231+ }
232232+ return false
233233+}
234234+235235+func (f *formatter) importSpec(x *ast.ImportSpec) {
236236+ if x.Name != nil {
237237+ f.label(x.Name)
238238+ f.print(blank)
239239+ } else {
240240+ f.current.pos++
241241+ f.visitComments(f.current.pos)
242242+ }
243243+ f.expr(x.Path)
244244+ f.print(newline)
245245+}
246246+247247+func (f *formatter) label(l ast.Label) {
248248+ switch n := l.(type) {
249249+ case *ast.Ident:
250250+ f.print(n.NamePos, n)
251251+252252+ case *ast.BasicLit:
253253+ if f.cfg.simplify && n.Kind == token.STRING && len(n.Value) > 2 {
254254+ s := n.Value
255255+ unquoted, err := strconv.Unquote(s)
256256+ fset := token.NewFileSet()
257257+ if err == nil {
258258+ e, _ := parser.ParseExpr(fset, "check", unquoted)
259259+ if _, ok := e.(*ast.Ident); ok {
260260+ f.print(n.ValuePos, unquoted)
261261+ break
262262+ }
263263+ }
264264+ }
265265+ f.print(n.ValuePos, n.Value)
266266+267267+ case *ast.TemplateLabel:
268268+ f.print(n.Langle, token.LSS, indent)
269269+ f.label(n.Ident)
270270+ f.print(unindent, n.Rangle, token.GTR)
271271+272272+ case *ast.Interpolation:
273273+ f.expr(n)
274274+275275+ case *ast.ExprLabel:
276276+ f.print(n.Lbrack, token.LBRACK, indent)
277277+ f.expr(n.Label)
278278+ f.print(unindent, n.Rbrack, token.RBRACK)
279279+280280+ default:
281281+ panic(fmt.Sprintf("unknown label type %T", n))
282282+ }
283283+}
284284+285285+func (f *formatter) expr(x ast.Expr) {
286286+ const depth = 1
287287+ f.expr1(x, token.LowestPrec, depth)
288288+}
289289+290290+func (f *formatter) expr0(x ast.Expr, depth int) {
291291+ f.expr1(x, token.LowestPrec, depth)
292292+}
293293+294294+func (f *formatter) expr1(expr ast.Expr, prec1, depth int) {
295295+ if f.before(expr) {
296296+ f.exprRaw(expr, prec1, depth)
297297+ }
298298+ f.after(expr)
299299+}
300300+301301+func (f *formatter) exprRaw(expr ast.Expr, prec1, depth int) {
302302+303303+ switch x := expr.(type) {
304304+ case *ast.BadExpr:
305305+ f.print(x.From, "BadExpr")
306306+307307+ case *ast.BottomLit:
308308+ f.print(x.Bottom, token.BOTTOM)
309309+310310+ case *ast.Ident:
311311+ f.print(x.NamePos, x)
312312+313313+ case *ast.BinaryExpr:
314314+ if depth < 1 {
315315+ f.internalError("depth < 1:", depth)
316316+ depth = 1
317317+ }
318318+ if prec1 == 8 { // ..
319319+ prec1 = 9 // always parentheses for values of ranges
320320+ }
321321+ f.binaryExpr(x, prec1, cutoff(x, depth), depth)
322322+323323+ case *ast.UnaryExpr:
324324+ const prec = token.UnaryPrec
325325+ if prec < prec1 {
326326+ // parenthesis needed
327327+ f.print(token.LPAREN, nooverride)
328328+ f.expr(x)
329329+ f.print(token.RPAREN)
330330+ } else {
331331+ // no parenthesis needed
332332+ f.print(x.OpPos, x.Op, nooverride)
333333+ f.expr1(x.X, prec, depth)
334334+ }
335335+336336+ case *ast.BasicLit:
337337+ f.print(x.ValuePos, x)
338338+339339+ case *ast.Interpolation:
340340+ f.before(nil)
341341+ for _, x := range x.Elts {
342342+ f.expr0(x, depth+1)
343343+ }
344344+ f.after(nil)
345345+346346+ case *ast.ParenExpr:
347347+ if _, hasParens := x.X.(*ast.ParenExpr); hasParens {
348348+ // don't print parentheses around an already parenthesized expression
349349+ // TODO: consider making this more general and incorporate precedence levels
350350+ f.expr0(x.X, depth)
351351+ } else {
352352+ f.print(x.Lparen, token.LPAREN)
353353+ f.expr0(x.X, reduceDepth(depth)) // parentheses undo one level of depth
354354+ f.print(x.Rparen, token.RPAREN)
355355+ }
356356+357357+ case *ast.SelectorExpr:
358358+ f.selectorExpr(x, depth)
359359+360360+ case *ast.IndexExpr:
361361+ f.expr1(x.X, token.HighestPrec, 1)
362362+ f.print(x.Lbrack, token.LBRACK)
363363+ f.expr0(x.Index, depth+1)
364364+ f.print(x.Rbrack, token.RBRACK)
365365+366366+ case *ast.SliceExpr:
367367+ f.expr1(x.X, token.HighestPrec, 1)
368368+ f.print(x.Lbrack, token.LBRACK)
369369+ indices := []ast.Expr{x.Low, x.High}
370370+ for i, y := range indices {
371371+ if i > 0 {
372372+ // blanks around ":" if both sides exist and either side is a binary expression
373373+ x := indices[i-1]
374374+ if depth <= 1 && x != nil && y != nil && (isBinary(x) || isBinary(y)) {
375375+ f.print(blank, token.COLON, blank)
376376+ } else {
377377+ f.print(token.COLON)
378378+ }
379379+ }
380380+ if y != nil {
381381+ f.expr0(y, depth+1)
382382+ }
383383+ }
384384+ f.print(x.Rbrack, token.RBRACK)
385385+386386+ case *ast.CallExpr:
387387+ if len(x.Args) > 1 {
388388+ depth++
389389+ }
390390+ wasIndented := f.possibleSelectorExpr(x.Fun, token.HighestPrec, depth)
391391+ f.print(x.Lparen, token.LPAREN)
392392+ f.walkExprList(x.Args, depth)
393393+ f.print(trailcomma, noblank, x.Rparen, token.RPAREN)
394394+ if wasIndented {
395395+ f.print(unindent)
396396+ }
397397+398398+ case *ast.Ellipsis:
399399+ f.print(x.Ellipsis, token.ELLIPSIS)
400400+ if x.Elt != nil {
401401+ f.expr(x.Elt) // TODO
402402+ }
403403+404404+ case *ast.StructLit:
405405+ f.print(x.Lbrace, token.LBRACE, noblank, f.formfeed(), indent)
406406+ f.walkDeclList(x.Elts)
407407+ f.matchUnindent()
408408+ f.print(noblank, x.Rbrace, token.RBRACE)
409409+410410+ case *ast.ListLit:
411411+ f.print(x.Lbrack, token.LBRACK, indent)
412412+ f.walkExprList(x.Elts, 1)
413413+ if x.Ellipsis != token.NoPos || x.Type != nil {
414414+ f.print(x.Ellipsis, token.ELLIPSIS)
415415+ if x.Type != nil && !isTop(x.Type) {
416416+ f.expr(x.Type)
417417+ }
418418+ } else {
419419+ f.print(trailcomma, noblank)
420420+ f.current.pos += 2
421421+ f.visitComments(f.current.pos)
422422+ }
423423+ f.matchUnindent()
424424+ f.print(noblank, x.Rbrack, token.RBRACK)
425425+426426+ case *ast.ListComprehension:
427427+ f.print(x.Lbrack, token.LBRACK, blank, indent)
428428+ f.expr(x.Expr)
429429+ f.print(blank)
430430+ f.walkClauseList(x.Clauses)
431431+ f.print(unindent, f.wsOverride(blank), x.Rbrack, token.RBRACK)
432432+433433+ case *ast.LambdaExpr:
434434+ f.print(x.Lparen, token.LPAREN, indent, noblank)
435435+436436+ f.before(nil)
437437+ for _, x := range x.Params {
438438+ f.label(x.Label)
439439+ if x.Colon.IsValid() {
440440+ f.print(x.Colon, token.COLON, blank)
441441+ f.expr(x.Value)
442442+ }
443443+ f.print(comma, blank)
444444+ }
445445+ f.print(trailcomma, noblank)
446446+ f.after(nil)
447447+448448+ f.print(trailcomma, noblank, unindent)
449449+ f.print(x.Rparen, token.RPAREN, blank)
450450+ f.print(token.LAMBDA, blank)
451451+ f.expr(x.Expr)
452452+453453+ default:
454454+ panic(fmt.Sprintf("unimplemented type %T", x))
455455+ }
456456+ return
457457+}
458458+459459+func (f *formatter) clause(clause ast.Clause) {
460460+ switch n := clause.(type) {
461461+ case *ast.ForClause:
462462+ f.print(blank, n.For, "for", blank)
463463+ if n.Key != nil {
464464+ f.label(n.Key)
465465+ f.print(n.Colon, token.COMMA, blank)
466466+ } else {
467467+ f.current.pos++
468468+ f.visitComments(f.current.pos)
469469+ }
470470+ f.label(n.Value)
471471+ f.print(blank, n.In, "in", blank)
472472+ f.expr(n.Source)
473473+474474+ case *ast.IfClause:
475475+ f.print(blank, n.If, "if", blank)
476476+ f.expr(n.Condition)
477477+478478+ default:
479479+ panic("unknown clause type")
480480+ }
481481+}
482482+483483+func walkBinary(e *ast.BinaryExpr) (has6, has7, has8 bool, maxProblem int) {
484484+ switch e.Op.Precedence() {
485485+ case 6:
486486+ has6 = true
487487+ case 7:
488488+ has7 = true
489489+ case 8:
490490+ has8 = true
491491+ }
492492+493493+ switch l := e.X.(type) {
494494+ case *ast.BinaryExpr:
495495+ if l.Op.Precedence() < e.Op.Precedence() {
496496+ // parens will be inserted.
497497+ // pretend this is an *syntax.ParenExpr and do nothing.
498498+ break
499499+ }
500500+ h6, h7, h8, mp := walkBinary(l)
501501+ has6 = has6 || h6
502502+ has7 = has7 || h7
503503+ has8 = has8 || h8
504504+ if maxProblem < mp {
505505+ maxProblem = mp
506506+ }
507507+ }
508508+509509+ switch r := e.Y.(type) {
510510+ case *ast.BinaryExpr:
511511+ if r.Op.Precedence() <= e.Op.Precedence() {
512512+ // parens will be inserted.
513513+ // pretend this is an *syntax.ParenExpr and do nothing.
514514+ break
515515+ }
516516+ h6, h7, h8, mp := walkBinary(r)
517517+ has6 = has6 || h6
518518+ has7 = has7 || h7
519519+ has8 = has8 || h8
520520+ if maxProblem < mp {
521521+ maxProblem = mp
522522+ }
523523+524524+ case *ast.UnaryExpr:
525525+ switch e.Op.String() + r.Op.String() {
526526+ case "/*":
527527+ maxProblem = 8
528528+ case "++", "--":
529529+ if maxProblem < 6 {
530530+ maxProblem = 6
531531+ }
532532+ }
533533+ }
534534+ return
535535+}
536536+537537+func cutoff(e *ast.BinaryExpr, depth int) int {
538538+ has6, has7, has8, maxProblem := walkBinary(e)
539539+ if maxProblem > 0 {
540540+ return maxProblem + 1
541541+ }
542542+ if (has6 || has7) && has8 {
543543+ if depth == 1 {
544544+ return 8
545545+ }
546546+ if has7 {
547547+ return 7
548548+ }
549549+ return 6
550550+ }
551551+ if has6 && has7 {
552552+ if depth == 1 {
553553+ return 7
554554+ }
555555+ return 6
556556+ }
557557+ if depth == 1 {
558558+ return 8
559559+ }
560560+ return 6
561561+}
562562+563563+func diffPrec(expr ast.Expr, prec int) int {
564564+ x, ok := expr.(*ast.BinaryExpr)
565565+ if !ok || prec != x.Op.Precedence() {
566566+ return 1
567567+ }
568568+ return 0
569569+}
570570+571571+func reduceDepth(depth int) int {
572572+ depth--
573573+ if depth < 1 {
574574+ depth = 1
575575+ }
576576+ return depth
577577+}
578578+579579+// Format the binary expression: decide the cutoff and then format.
580580+// Let's call depth == 1 Normal mode, and depth > 1 Compact mode.
581581+// (Algorithm suggestion by Russ Cox.)
582582+//
583583+// The precedences are:
584584+// 8 ..
585585+// 7 * / % quo rem div mod
586586+// 6 + -
587587+// 5 == != < <= > >=
588588+// 4 &&
589589+// 3 ||
590590+// 2 &
591591+// 1 |
592592+//
593593+// The only decision is whether there will be spaces around levels 6 and 7.
594594+// There are never spaces at level 8 (unary), and always spaces at levels 5 and below.
595595+//
596596+// To choose the cutoff, look at the whole expression but excluding primary
597597+// expressions (function calls, parenthesized exprs), and apply these rules:
598598+//
599599+// 1) If there is a binary operator with a right side unary operand
600600+// that would clash without a space, the cutoff must be (in order):
601601+//
602602+// /* 8
603603+// ++ 7 // not necessary, but to avoid confusion
604604+// -- 7
605605+//
606606+// (Comparison operators always have spaces around them.)
607607+//
608608+// 2) If there is a mix of level 7 and level 6 operators, then the cutoff
609609+// is 7 (use spaces to distinguish precedence) in Normal mode
610610+// and 6 (never use spaces) in Compact mode.
611611+//
612612+// 3) If there are no level 6 operators or no level 7 operators, then the
613613+// cutoff is 8 (always use spaces) in Normal mode
614614+// and 6 (never use spaces) in Compact mode.
615615+//
616616+func (f *formatter) binaryExpr(x *ast.BinaryExpr, prec1, cutoff, depth int) {
617617+ f.nestExpr++
618618+ defer func() { f.nestExpr-- }()
619619+620620+ prec := x.Op.Precedence()
621621+ if prec < prec1 {
622622+ // parenthesis needed
623623+ // Note: The parser inserts an syntax.ParenExpr node; thus this case
624624+ // can only occur if the AST is created in a different way.
625625+ // defer p.pushComment(nil).pop()
626626+ f.print(token.LPAREN, nooverride)
627627+ f.expr0(x, reduceDepth(depth)) // parentheses undo one level of depth
628628+ f.print(token.RPAREN)
629629+ return
630630+ }
631631+632632+ printBlank := prec < cutoff
633633+634634+ ws := indent
635635+ f.expr1(x.X, prec, depth+diffPrec(x.X, prec))
636636+ f.print(nooverride)
637637+ if printBlank {
638638+ f.print(blank)
639639+ }
640640+ f.print(x.OpPos, x.Op)
641641+ if x.Y.Pos().IsNewline() {
642642+ // at least one line break, but respect an extra empty line
643643+ // in the source
644644+ f.print(formfeed)
645645+ printBlank = false // no blank after line break
646646+ } else {
647647+ f.print(nooverride)
648648+ }
649649+ if printBlank {
650650+ f.print(blank)
651651+ }
652652+ f.expr1(x.Y, prec+1, depth+1)
653653+ if ws == ignore {
654654+ f.print(unindent)
655655+ }
656656+}
657657+658658+func isBinary(expr ast.Expr) bool {
659659+ _, ok := expr.(*ast.BinaryExpr)
660660+ return ok
661661+}
662662+663663+func (f *formatter) possibleSelectorExpr(expr ast.Expr, prec1, depth int) bool {
664664+ if x, ok := expr.(*ast.SelectorExpr); ok {
665665+ return f.selectorExpr(x, depth)
666666+ }
667667+ f.expr1(expr, prec1, depth)
668668+ return false
669669+}
670670+671671+// selectorExpr handles an *syntax.SelectorExpr node and returns whether x spans
672672+// multiple lines.
673673+func (f *formatter) selectorExpr(x *ast.SelectorExpr, depth int) bool {
674674+ f.expr1(x.X, token.HighestPrec, depth)
675675+ f.print(token.PERIOD)
676676+ if x.Sel.Pos().IsNewline() {
677677+ f.print(indent, formfeed, x.Sel.Pos(), x.Sel)
678678+ f.print(unindent)
679679+ return true
680680+ }
681681+ f.print(x.Sel.Pos(), x.Sel)
682682+ return false
683683+}
684684+685685+func isTop(e ast.Expr) bool {
686686+ ident, ok := e.(*ast.Ident)
687687+ return ok && ident.Name == "_"
688688+}
+369
cue/format/printer.go
···11+// Copyright 2018 The CUE Authors
22+//
33+// Licensed under the Apache License, Version 2.0 (the "License");
44+// you may not use this file except in compliance with the License.
55+// You may obtain a copy of the License at
66+//
77+// http://www.apache.org/licenses/LICENSE-2.0
88+//
99+// Unless required by applicable law or agreed to in writing, software
1010+// distributed under the License is distributed on an "AS IS" BASIS,
1111+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212+// See the License for the specific language governing permissions and
1313+// limitations under the License.
1414+1515+package format
1616+1717+import (
1818+ "fmt"
1919+ "os"
2020+ "text/tabwriter"
2121+2222+ "cuelang.org/go/cue/ast"
2323+ "cuelang.org/go/cue/token"
2424+)
2525+2626+// A printer takes the stream of formatting tokens and spacing directives
2727+// produced by the formatter and adjusts the spacing based on the original
2828+// source code.
2929+type printer struct {
3030+ w tabwriter.Writer
3131+3232+ fset *token.FileSet
3333+ cfg *config
3434+3535+ allowed whiteSpace
3636+ requested whiteSpace
3737+ indentStack []whiteSpace
3838+ indentPos int
3939+4040+ pos token.Position // current pos in AST
4141+4242+ lastTok token.Token // last token printed (syntax.ILLEGAL if it's whitespace)
4343+4444+ output []byte
4545+ indent int
4646+ spaceBefore bool
4747+}
4848+4949+func (p *printer) init(cfg *config) {
5050+ p.cfg = cfg
5151+ p.pos = token.Position{Line: 1, Column: 1}
5252+}
5353+5454+const debug = false
5555+5656+func (p *printer) internalError(msg ...interface{}) {
5757+ if debug {
5858+ fmt.Print(p.pos.String() + ": ")
5959+ fmt.Println(msg...)
6060+ panic("go/printer")
6161+ }
6262+}
6363+6464+func (p *printer) lineFor(pos token.Pos) int {
6565+ if p.fset == nil {
6666+ return 0
6767+ }
6868+ return p.fset.Position(pos).Line
6969+}
7070+7171+func (p *printer) Print(v interface{}) {
7272+ var (
7373+ impliedComma = false
7474+ isLit bool
7575+ data string
7676+ nextWS whiteSpace
7777+ )
7878+ switch x := v.(type) {
7979+ case token.Token:
8080+ s := x.String()
8181+ before, after := mayCombine(p.lastTok, x)
8282+ if before && !p.spaceBefore {
8383+ // the previous and the current token must be
8484+ // separated by a blank otherwise they combine
8585+ // into a different incorrect token sequence
8686+ // (except for syntax.INT followed by a '.' this
8787+ // should never happen because it is taken care
8888+ // of via binary expression formatting)
8989+ if p.allowed&blank != 0 {
9090+ p.internalError("whitespace buffer not empty")
9191+ }
9292+ p.allowed |= blank
9393+ }
9494+ if after {
9595+ nextWS = blank
9696+ }
9797+ data = s
9898+ switch x {
9999+ case token.EOF:
100100+ data = ""
101101+ p.allowed = newline
102102+ p.allowed &^= newsection
103103+ case token.LPAREN, token.LBRACK, token.LBRACE:
104104+ case token.RPAREN, token.RBRACK, token.RBRACE:
105105+ impliedComma = true
106106+ }
107107+ p.lastTok = x
108108+109109+ case *ast.BasicLit:
110110+ data = x.Value
111111+ isLit = true
112112+ impliedComma = true
113113+ p.lastTok = x.Kind
114114+115115+ case *ast.Ident:
116116+ data = x.Name
117117+ impliedComma = true
118118+ p.lastTok = token.IDENT
119119+120120+ case string:
121121+ data = x
122122+ impliedComma = true
123123+ p.lastTok = token.STRING
124124+125125+ case *ast.CommentGroup:
126126+ rel := x.Pos().RelPos()
127127+ if x.Line { // TODO: we probably don't need this.
128128+ rel = token.Blank
129129+ }
130130+ switch rel {
131131+ case token.NoRelPos:
132132+ case token.Newline, token.NewSection:
133133+ case token.Blank, token.Elided:
134134+ p.allowed |= blank
135135+ fallthrough
136136+ case token.NoSpace:
137137+ p.allowed &^= newline | newsection | formfeed | declcomma
138138+ }
139139+ return
140140+141141+ case *ast.Comment:
142142+ // TODO: if implied comma, postpone comment
143143+ data = x.Text
144144+ p.lastTok = token.COMMENT
145145+ break
146146+147147+ case whiteSpace:
148148+ p.allowed |= x
149149+ return
150150+151151+ case token.Pos:
152152+ // TODO: should we use a known file position to synchronize? Go does,
153153+ // but we don't really have to.
154154+ // pos := p.fset.Position(x)
155155+ if x.HasRelPos() {
156156+ if p.allowed&nooverride == 0 {
157157+ requested := p.allowed
158158+ switch x.RelPos() {
159159+ case token.NoSpace:
160160+ requested &^= newline | newsection | formfeed
161161+ case token.Blank:
162162+ requested |= blank
163163+ requested &^= newline | newsection | formfeed
164164+ case token.Newline:
165165+ requested |= newline
166166+ case token.NewSection:
167167+ requested |= newsection
168168+ }
169169+ p.writeWhitespace(requested)
170170+ p.allowed = 0
171171+ p.requested = 0
172172+ }
173173+ // p.pos = pos
174174+ }
175175+ return
176176+177177+ default:
178178+ fmt.Fprintf(os.Stderr, "print: unsupported argument %v (%T)\n", x, x)
179179+ panic("go/printer type")
180180+ }
181181+182182+ p.writeWhitespace(p.allowed)
183183+ p.allowed = 0
184184+ p.requested = 0
185185+ p.writeString(data, isLit)
186186+ p.allowed = nextWS
187187+ _ = impliedComma // TODO: delay comment printings
188188+}
189189+190190+func (p *printer) writeWhitespace(ws whiteSpace) {
191191+ if debug {
192192+ p.output = append(p.output, fmt.Sprintf("/*=%x=*/", p.allowed)...) // do not update f.pos!
193193+ }
194194+195195+ if ws&comma != 0 {
196196+ switch {
197197+ case ws&(newsection|newline|formfeed) != 0,
198198+ ws&trailcomma == 0:
199199+ p.writeByte(',', 1)
200200+ }
201201+ }
202202+ if ws&indent != 0 {
203203+ p.markLineIndent(ws)
204204+ }
205205+ if ws&unindent != 0 {
206206+ p.markUnindentLine()
207207+ }
208208+ switch {
209209+ case ws&newsection != 0:
210210+ p.maybeIndentLine(ws)
211211+ p.writeByte('\f', 2)
212212+ p.spaceBefore = true
213213+ case ws&formfeed != 0:
214214+ p.maybeIndentLine(ws)
215215+ p.writeByte('\f', 1)
216216+ p.spaceBefore = true
217217+ case ws&newline != 0:
218218+ p.maybeIndentLine(ws)
219219+ p.writeByte('\n', 1)
220220+ p.spaceBefore = true
221221+ case ws&declcomma != 0:
222222+ p.writeByte(',', 1)
223223+ p.writeByte(' ', 1)
224224+ p.spaceBefore = true
225225+ case ws&noblank != 0:
226226+ case ws&vtab != 0:
227227+ p.writeByte('\v', 1)
228228+ p.spaceBefore = true
229229+ case ws&blank != 0:
230230+ p.writeByte(' ', 1)
231231+ p.spaceBefore = true
232232+ }
233233+}
234234+235235+func (p *printer) markLineIndent(ws whiteSpace) {
236236+ p.indentStack = append(p.indentStack, ws)
237237+}
238238+239239+func (p *printer) markUnindentLine() (wasUnindented bool) {
240240+ last := len(p.indentStack) - 1
241241+ if ws := p.indentStack[last]; ws&indented != 0 {
242242+ p.indent--
243243+ wasUnindented = true
244244+ }
245245+ p.indentStack = p.indentStack[:last]
246246+ return wasUnindented
247247+}
248248+249249+func (p *printer) maybeIndentLine(ws whiteSpace) {
250250+ if ws&unindent == 0 && len(p.indentStack) > 0 {
251251+ last := len(p.indentStack) - 1
252252+ if ws := p.indentStack[last]; ws&indented != 0 || ws&indent == 0 {
253253+ return
254254+ }
255255+ p.indentStack[last] |= indented
256256+ p.indent++
257257+ }
258258+}
259259+260260+func (f *formatter) matchUnindent() whiteSpace {
261261+ f.allowed |= unindent
262262+ // TODO: make this work. Whitespace from closing bracket should match that
263263+ // of opening if there is no position information.
264264+ // f.allowed &^= nooverride | newline | newsection | formfeed | blank | noblank
265265+ // ws := f.indentStack[len(f.indentStack)-1]
266266+ // mask := blank | noblank | vtab
267267+ // f.allowed |= unindent | blank | noblank
268268+ // if ws&newline != 0 || ws*indented != 0 {
269269+ // f.allowed |= newline
270270+ // }
271271+ return 0
272272+}
273273+274274+// writeString writes the string s to p.output and updates p.pos, p.out,
275275+// and p.last. If isLit is set, s is escaped w/ tabwriter.Escape characters
276276+// to protect s from being interpreted by the tabwriter.
277277+//
278278+// Note: writeString is only used to write Go tokens, literals, and
279279+// comments, all of which must be written literally. Thus, it is correct
280280+// to always set isLit = true. However, setting it explicitly only when
281281+// needed (i.e., when we don't know that s contains no tabs or line breaks)
282282+// avoids processing extra escape characters and reduces run time of the
283283+// printer benchmark by up to 10%.
284284+//
285285+func (p *printer) writeString(s string, isLit bool) {
286286+ if s != "" {
287287+ p.spaceBefore = false
288288+ }
289289+290290+ if isLit {
291291+ // Protect s such that is passes through the tabwriter
292292+ // unchanged. Note that valid Go programs cannot contain
293293+ // tabwriter.Escape bytes since they do not appear in legal
294294+ // UTF-8 sequences.
295295+ p.output = append(p.output, tabwriter.Escape)
296296+ }
297297+298298+ if debug {
299299+ p.output = append(p.output, fmt.Sprintf("/*%s*/", p.pos)...) // do not update f.pos!
300300+ }
301301+ p.output = append(p.output, s...)
302302+303303+ if isLit {
304304+ p.output = append(p.output, tabwriter.Escape)
305305+ }
306306+ // update positions
307307+ nLines := 0
308308+ var li int // index of last newline; valid if nLines > 0
309309+ for i := 0; i < len(s); i++ {
310310+ // CUE tokens cannot contain '\f' - no need to look for it
311311+ if s[i] == '\n' {
312312+ nLines++
313313+ li = i
314314+ }
315315+ }
316316+ p.pos.Offset += len(s)
317317+ if nLines > 0 {
318318+ p.pos.Line += nLines
319319+ c := len(s) - li
320320+ p.pos.Column = c
321321+ } else {
322322+ p.pos.Column += len(s)
323323+ }
324324+}
325325+326326+func (p *printer) writeByte(ch byte, n int) {
327327+ for i := 0; i < n; i++ {
328328+ p.output = append(p.output, ch)
329329+ }
330330+331331+ // update positions
332332+ p.pos.Offset += n
333333+ if ch == '\n' || ch == '\f' {
334334+ p.pos.Line += n
335335+ p.pos.Column = 1
336336+337337+ n := p.cfg.Indent + p.indent // include base indentation
338338+ for i := 0; i < n; i++ {
339339+ p.output = append(p.output, '\t')
340340+ }
341341+342342+ // update positions
343343+ p.pos.Offset += n
344344+ p.pos.Column += n
345345+346346+ return
347347+ }
348348+ p.pos.Column += n
349349+}
350350+351351+func mayCombine(prev, next token.Token) (before, after bool) {
352352+ s := next.String()
353353+ if 'a' <= s[0] && s[0] < 'z' {
354354+ return true, true
355355+ }
356356+ switch prev {
357357+ case token.IQUO, token.IREM, token.IDIV, token.IMOD:
358358+ return false, false
359359+ case token.INT:
360360+ before = next == token.PERIOD // 1.
361361+ case token.ADD:
362362+ before = s[0] == '+' // ++
363363+ case token.SUB:
364364+ before = s[0] == '-' // --
365365+ case token.QUO:
366366+ before = s[0] == '*' // /*
367367+ }
368368+ return false, false
369369+}
+19
cue/format/testdata/comments.golden
···11+// Package line 1 group 1
22+// Package line 2 group 1
33+44+// Package line 1 - group 2
55+// Package line 2 - group 2
66+package comments
77+88+// Emit line 1 group 1
99+1010+// Emit line 1 group 2
1111+// Emit line 2 group 2
1212+{
1313+ // Inside Emit
1414+}
1515+1616+a: 3 // a line comment
1717+1818+b: 4 // line comment that is last in the file.
1919+cc: 555 // align comments
+19
cue/format/testdata/comments.input
···11+// Package line 1 group 1
22+// Package line 2 group 1
33+44+// Package line 1 - group 2
55+// Package line 2 - group 2
66+package comments
77+88+// Emit line 1 group 1
99+1010+// Emit line 1 group 2
1111+// Emit line 2 group 2
1212+{
1313+ // Inside Emit
1414+}
1515+1616+a: 3 // a line comment
1717+1818+b: 4 // line comment that is last in the file.
1919+cc: 555 // align comments
+163
cue/format/testdata/expressions.golden
···11+package expressions
22+33+{
44+ a: 1
55+ aaa: 2
66+77+ b: 3
88+99+ c b a: 4
1010+ c bb aaa: 5
1111+ c b <Name> a: int
1212+ alias = 3.14
1313+1414+ alias2 = foo
1515+ aaalias = foo
1616+ b: bar
1717+1818+ bottom1: _|_
1919+ bottom2: _|_ // converted to compact symbol
2020+2121+ empty: {}
2222+ emptyNewLine: {
2323+2424+ }
2525+ someObject: {
2626+ a: 8
2727+ aa: 9
2828+ aaa: 10
2929+ }
3030+3131+ e: 1 + 2*3
3232+ e: 1 * 2 * 3 // error
3333+ e: 2..3
3434+ e: 2..(3 + 4)
3535+ ex: 2..3 + 4*5
3636+ e: (2..3)..4
3737+ e: 1 + 2 + 3 // error
3838+3939+ e: s[1+2]
4040+ e: s[1:2]
4141+ e: s[1+2 : 2+4]
4242+ e: s[2]
4343+ e: s[2*3]
4444+ e: s[1+2*3]
4545+4646+ e: f(3 + 4 + 5)
4747+ e: f(3 * 4 * 5)
4848+ e: f(3 + 4*5)
4949+5050+ e: f(3 + 4 div 5)
5151+5252+ e: 3 < 4 && 5 > 4
5353+ e: a || b && c || d
5454+5555+ e: a + +b*3
5656+ e: -a - -b
5757+5858+ e: b + c
5959+ e: b*c + d
6060+ e: a*b + c
6161+ e: a - b - c
6262+ e: a - (b - c)
6363+ e: a - b*c
6464+ e: a - (b * c)
6565+ e: a * b / c
6666+ e: a div b + 5
6767+ e: a / b
6868+ e: x[a | b]
6969+ e: x[a/b]
7070+ e: a & b
7171+ e: a + +b
7272+ e: a - -b
7373+ e: a div -b
7474+ e: x[a*-b]
7575+ e: x[a + +b]
7676+ e: len(longVariableName) * 2
7777+7878+ e: "\(a)"
7979+ e: 'aa \(aaa) aa'
8080+ e: "aa \(aaa)"
8181+8282+ e: [1, 2]
8383+ e: [1, 2, 3, 4,
8484+ 5, 6, 7, 8]
8585+ e: [1, 2, 3, 4,
8686+ 5, 6, 7, 8, // maybe force additional comma
8787+ ]
8888+ e: [...]
8989+ e: [
9090+ ...]
9191+ e: [...
9292+ ]
9393+ e: [1, 2, ...]
9494+ e: [1, 2,
9595+ ...]
9696+ e: [...int]
9797+ e: [...int]
9898+ e: [...int | float]
9999+ e: [ x for x in someObject if x > 9 ]
100100+ e: [ x
101101+ for x in someObject
102102+ if x > 9 ]
103103+ e: [ x
104104+ for x in someObject
105105+ if x > 9
106106+ ]
107107+108108+ [k]: v for k, v in someObject
109109+ [k]: v <-
110110+ for k, v in someObject
111111+112112+ e: {[k]: v <-
113113+ for k, v in someObject
114114+ if k > "a"
115115+ }
116116+117117+ e: {[k]: v for k, v in someObject if k > "a"}
118118+ e: {[k]: v <-
119119+ for k, v in someObject if k > "a"}
120120+121121+ e: {[k]: v <-
122122+ for k, v in someObject
123123+ if k > "a"}
124124+125125+ e: [{
126126+ a: 1, b: 2
127127+ }]
128128+129129+ e: [{
130130+ a: 1, b: 2
131131+ },
132132+ ]
133133+134134+ e: [{
135135+ a: 1, b: 2
136136+ }, {
137137+ c: 1, d: 2
138138+ }]
139139+140140+ e: [{
141141+ a: 1, b: 2
142142+ },
143143+ 3,
144144+ 4,
145145+ ]
146146+147147+ e: (a, b) -> a + b
148148+ e: (a,
149149+ b) -> a + b
150150+ e: (a, b) -> {
151151+ a: b
152152+ }
153153+ e: (a,
154154+ b,
155155+ ) ->
156156+ {
157157+ a: b
158158+ }
159159+160160+ e: e.f(1, 2)
161161+162162+ e: (3 + 4)
163163+}
+163
cue/format/testdata/expressions.input
···11+package expressions
22+33+{
44+ a: 1
55+ aaa: 2
66+77+ b: 3
88+99+ c b a: 4
1010+ c bb aaa: 5
1111+ c b <Name> a: int
1212+ alias = 3.14
1313+1414+ alias2 = foo
1515+ aaalias = foo
1616+ b: bar
1717+1818+ bottom1: _|_
1919+ bottom2: _|_ // converted to compact symbol
2020+2121+ empty: {}
2222+ emptyNewLine: {
2323+2424+ }
2525+ someObject: {
2626+ a: 8
2727+ aa: 9
2828+ aaa: 10
2929+ }
3030+3131+ e: 1+2*3
3232+ e: 1*2*3 // error
3333+ e: 2..3
3434+ e: 2..(3 + 4)
3535+ ex: 2..3+4*5
3636+ e: 2..3..4
3737+ e: 1 + 2 + 3 // error
3838+3939+ e: s[1+2]
4040+ e: s[1:2]
4141+ e: s[1+2:2+4]
4242+ e: s[2]
4343+ e: s[2*3]
4444+ e: s[1+2*3]
4545+4646+ e: f(3+4+5)
4747+ e: f(3*4*5)
4848+ e: f(3+4*5)
4949+5050+ e: f(3 + 4 div 5)
5151+5252+ e: 3<4&&5>4
5353+ e: a || b && c || d
5454+5555+ e: a + +b * 3
5656+ e: -a - -b
5757+5858+ e: b + c
5959+ e: b*c + d
6060+ e: a*b + c
6161+ e: a - b - c
6262+ e: a - (b - c)
6363+ e: a - b*c
6464+ e: a - (b * c)
6565+ e: a * b / c
6666+ e: a div b + 5
6767+ e: a / b
6868+ e: x[a|b]
6969+ e: x[a /b]
7070+ e: a & b
7171+ e: a + +b
7272+ e: a - -b
7373+ e: a div - b
7474+ e: x[a*-b]
7575+ e: x[a + +b]
7676+ e: len(longVariableName) * 2
7777+7878+ e: "\(a)"
7979+ e: 'aa \(aaa) aa'
8080+ e: "aa \(aaa)"
8181+8282+ e: [1, 2]
8383+ e: [1, 2, 3, 4,
8484+ 5, 6, 7, 8]
8585+ e: [1, 2, 3, 4,
8686+ 5, 6, 7, 8 // maybe force additional comma
8787+ ]
8888+ e: [...]
8989+ e: [
9090+ ...]
9191+ e: [...
9292+ ]
9393+ e: [1, 2, ...]
9494+ e: [1, 2,
9595+ ...]
9696+ e: [...int]
9797+ e: [...int,]
9898+ e: [...int | float]
9999+ e: [ x for x in someObject if x > 9 ]
100100+ e: [ x
101101+ for x in someObject
102102+ if x > 9 ]
103103+ e: [ x
104104+ for x in someObject
105105+ if x > 9
106106+ ]
107107+108108+ [k]: v for k, v in someObject
109109+ [k]: v <-
110110+ for k, v in someObject
111111+112112+ e: { [k]:v <-
113113+ for k, v in someObject
114114+ if k > "a"
115115+ }
116116+117117+ e: { [k]:v for k, v in someObject if k > "a"}
118118+ e: { [k]:v <-
119119+ for k, v in someObject if k > "a"}
120120+121121+ e: { [k]:v <-
122122+ for k, v in someObject
123123+ if k > "a"}
124124+125125+ e: [{
126126+ a: 1, b: 2,
127127+ }]
128128+129129+ e: [{
130130+ a: 1, b: 2,
131131+ },
132132+ ]
133133+134134+ e: [{
135135+ a: 1, b: 2,
136136+ }, {
137137+ c: 1, d: 2,
138138+ }]
139139+140140+ e: [{
141141+ a: 1, b: 2,
142142+ },
143143+ 3,
144144+ 4,
145145+ ]
146146+147147+ e: (a, b) -> a + b
148148+ e: (a,
149149+ b) -> a + b
150150+ e: (a, b) -> {
151151+ a: b
152152+ }
153153+ e: (a,
154154+ b
155155+ ) ->
156156+ {
157157+ a: b
158158+ }
159159+160160+ e: e.f(1, 2)
161161+162162+ e: ((3 + 4))
163163+}
+6
cue/format/testdata/simplify.golden
···11+22+foo bar: "str"
33+44+a B: 42
55+66+"a.b" "foo-" cc_dd: x
+5
cue/format/testdata/simplify.input
···11+"foo" "bar": "str"
22+33+a "B": 42
44+55+"a.b" "foo-" "cc_dd": x