this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

cue/format: add package

Change-Id: I09091c053009fc29d1426e1037f67933c805d158

+2149
+302
cue/format/format.go
··· 1 + // Copyright 2018 The CUE Authors 2 + // 3 + // Licensed under the Apache License, Version 2.0 (the "License"); 4 + // you may not use this file except in compliance with the License. 5 + // You may obtain a copy of the License at 6 + // 7 + // http://www.apache.org/licenses/LICENSE-2.0 8 + // 9 + // Unless required by applicable law or agreed to in writing, software 10 + // distributed under the License is distributed on an "AS IS" BASIS, 11 + // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 + // See the License for the specific language governing permissions and 13 + // limitations under the License. 14 + 15 + // Package format implements standard formatting of CUE configurations. 16 + package format // import "cuelang.org/go/cue/format" 17 + 18 + import ( 19 + "bytes" 20 + "fmt" 21 + "io" 22 + "strings" 23 + "text/tabwriter" 24 + 25 + "cuelang.org/go/cue/ast" 26 + "cuelang.org/go/cue/parser" 27 + "cuelang.org/go/cue/token" 28 + ) 29 + 30 + // An Option sets behavior of the formatter. 31 + type Option func(c *config) 32 + 33 + // FileSet sets the file set that was used for creating a node. The formatter 34 + // generally formats fine without it. 35 + func FileSet(fset *token.FileSet) Option { 36 + return func(c *config) { c.fset = fset } 37 + } 38 + 39 + // Simplify allows the formatter to simplify output, such as removing 40 + // unnecessary quotes. 41 + func Simplify() Option { 42 + return func(c *config) { c.simplify = true } 43 + } 44 + 45 + // TODO: other options: 46 + // 47 + // const ( 48 + // RawFormat Mode = 1 << iota // do not use a tabwriter; if set, UseSpaces is ignored 49 + // TabIndent // use tabs for indentation independent of UseSpaces 50 + // UseSpaces // use spaces instead of tabs for alignment 51 + // SourcePos // emit //line comments to preserve original source positions 52 + // ) 53 + 54 + // Node formats node in canonical cue fmt style and writes the result to dst. 55 + // 56 + // The node type must be *ast.File, []syntax.Decl, syntax.Expr, syntax.Decl, or 57 + // syntax.Spec. Node does not modify node. Imports are not sorted for nodes 58 + // representing partial source files (for instance, if the node is not an 59 + // *ast.File). 60 + // 61 + // The function may return early (before the entire result is written) and 62 + // return a formatting error, for instance due to an incorrect AST. 63 + // 64 + func Node(dst io.Writer, node ast.Node, opt ...Option) error { 65 + cfg := newConfig(opt) 66 + return cfg.fprint(dst, node) 67 + } 68 + 69 + // Source formats src in canonical cue fmt style and returns the result or an 70 + // (I/O or syntax) error. src is expected to be a syntactically correct CUE 71 + // source file, or a list of CUE declarations or statements. 72 + // 73 + // If src is a partial source file, the leading and trailing space of src is 74 + // applied to the result (such that it has the same leading and trailing space 75 + // as src), and the result is indented by the same amount as the first line of 76 + // src containing code. Imports are not sorted for partial source files. 77 + // 78 + // Caution: Tools relying on consistent formatting based on the installed 79 + // version of cue (for instance, such as for presubmit checks) should execute 80 + // that cue binary instead of calling Source. 81 + // 82 + func Source(b []byte, opt ...Option) ([]byte, error) { 83 + cfg := newConfig(opt) 84 + if cfg.fset == nil { 85 + cfg.fset = token.NewFileSet() 86 + } 87 + 88 + f, err := parser.ParseFile(cfg.fset, "", b, 89 + parser.ParseComments, parser.ParseLambdas) 90 + if err != nil { 91 + return nil, fmt.Errorf("parse: %s", err) 92 + } 93 + 94 + // print AST 95 + var buf bytes.Buffer 96 + if err := cfg.fprint(&buf, f); err != nil { 97 + return nil, fmt.Errorf("print: %s", err) 98 + } 99 + return buf.Bytes(), nil 100 + } 101 + 102 + type config struct { 103 + fset *token.FileSet 104 + 105 + UseSpaces bool 106 + TabIndent bool 107 + Tabwidth int // default: 8 108 + Indent int // default: 0 (all code is indented at least by this much) 109 + 110 + simplify bool 111 + } 112 + 113 + func newConfig(opt []Option) *config { 114 + cfg := &config{Tabwidth: 8, TabIndent: true, UseSpaces: true} 115 + for _, o := range opt { 116 + o(cfg) 117 + } 118 + return cfg 119 + } 120 + 121 + // Config defines the output of Fprint. 122 + func (cfg *config) fprint(output io.Writer, node interface{}) (err error) { 123 + // print node 124 + var p printer 125 + p.init(cfg) 126 + if err = printNode(node, &p); err != nil { 127 + return err 128 + } 129 + 130 + minwidth := cfg.Tabwidth 131 + 132 + padchar := byte('\t') 133 + if cfg.UseSpaces { 134 + padchar = ' ' 135 + } 136 + 137 + twmode := tabwriter.DiscardEmptyColumns | tabwriter.StripEscape 138 + if cfg.TabIndent { 139 + minwidth = 0 140 + twmode |= tabwriter.TabIndent 141 + } 142 + 143 + tw := tabwriter.NewWriter(output, minwidth, cfg.Tabwidth, 1, padchar, twmode) 144 + 145 + // write printer result via tabwriter/trimmer to output 146 + if _, err = tw.Write(p.output); err != nil { 147 + return 148 + } 149 + 150 + err = tw.Flush() 151 + return 152 + } 153 + 154 + // A formatter walks a syntax.Node, interspersed with comments and spacing 155 + // directives, in the order that they would occur in printed form. 156 + type formatter struct { 157 + *printer 158 + 159 + stack []frame 160 + current frame 161 + nestExpr int 162 + 163 + labelBuf []ast.Label 164 + } 165 + 166 + func newFormatter(p *printer) *formatter { 167 + f := &formatter{ 168 + printer: p, 169 + current: frame{ 170 + settings: settings{ 171 + nodeSep: newline, 172 + parentSep: newline, 173 + }, 174 + }, 175 + } 176 + return f 177 + } 178 + 179 + type whiteSpace int 180 + 181 + const ( 182 + ignore whiteSpace = 0 183 + 184 + // write a space, or disallow it 185 + blank whiteSpace = 1 << iota 186 + vtab // column marker 187 + noblank 188 + 189 + nooverride 190 + 191 + comma // print a comma, unless trailcomma overrides it 192 + trailcomma // print a trailing comma unless closed on same line 193 + declcomma // write a comma when not at the end of line 194 + 195 + newline // write a line in a table 196 + formfeed // next line is not part of the table 197 + newsection // add two newlines 198 + 199 + indent // request indent an extra level after the next newline 200 + unindent // unindent a level after the next newline 201 + indented // element was indented. 202 + ) 203 + 204 + type frame struct { 205 + cg []*ast.CommentGroup 206 + pos int8 207 + 208 + settings 209 + commentNewline bool 210 + } 211 + 212 + type settings struct { 213 + // separator is blank if the current node spans a single line and newline 214 + // otherwise. 215 + nodeSep whiteSpace 216 + parentSep whiteSpace 217 + override whiteSpace 218 + } 219 + 220 + func (f *formatter) print(a ...interface{}) { 221 + for _, x := range a { 222 + f.Print(x) 223 + switch x.(type) { 224 + case string, token.Token: // , *syntax.BasicLit, *syntax.Ident: 225 + f.current.pos++ 226 + } 227 + } 228 + f.visitComments(f.current.pos) 229 + } 230 + 231 + func (f *formatter) formfeed() whiteSpace { 232 + if f.current.nodeSep == blank { 233 + return blank 234 + } 235 + return formfeed 236 + } 237 + 238 + func (f *formatter) wsOverride(def whiteSpace) whiteSpace { 239 + if f.current.override == ignore { 240 + return def 241 + } 242 + return f.current.override 243 + } 244 + 245 + func (f *formatter) onOneLine(node ast.Node) bool { 246 + a := node.Pos() 247 + b := node.End() 248 + if f.fset != nil && a.IsValid() && b.IsValid() { 249 + return f.lineFor(a) == f.lineFor(b) 250 + } 251 + // TODO: walk and look at relative positions to determine the same? 252 + return false 253 + } 254 + 255 + func (f *formatter) before(node ast.Node) bool { 256 + f.stack = append(f.stack, f.current) 257 + f.current = frame{settings: f.current.settings} 258 + f.current.parentSep = f.current.nodeSep 259 + 260 + if node != nil { 261 + if f.current.nodeSep != blank && f.onOneLine(node) { 262 + f.current.nodeSep = blank 263 + } 264 + f.current.cg = node.Comments() 265 + f.visitComments(f.current.pos) 266 + return true 267 + } 268 + return false 269 + } 270 + 271 + func (f *formatter) after(node ast.Node) { 272 + f.visitComments(127) 273 + p := len(f.stack) - 1 274 + f.current = f.stack[p] 275 + f.stack = f.stack[:p] 276 + f.current.pos++ 277 + f.visitComments(f.current.pos) 278 + } 279 + 280 + func (f *formatter) visitComments(until int8) { 281 + c := &f.current 282 + 283 + for ; len(c.cg) > 0 && c.cg[0].Position <= until; c.cg = c.cg[1:] { 284 + f.Print(c.cg[0]) 285 + 286 + printBlank := false 287 + if c.cg[0].Doc { 288 + f.Print(newline) 289 + printBlank = true 290 + } 291 + for _, c := range c.cg[0].List { 292 + if !printBlank { 293 + f.Print(vtab) 294 + } 295 + f.Print(c.Slash) 296 + f.Print(c) 297 + if strings.HasPrefix(c.Text, "//") { 298 + f.Print(newline) 299 + } 300 + } 301 + } 302 + }
+415
cue/format/format_test.go
··· 1 + // Copyright 2018 The CUE Authors 2 + // 3 + // Licensed under the Apache License, Version 2.0 (the "License"); 4 + // you may not use this file except in compliance with the License. 5 + // You may obtain a copy of the License at 6 + // 7 + // http://www.apache.org/licenses/LICENSE-2.0 8 + // 9 + // Unless required by applicable law or agreed to in writing, software 10 + // distributed under the License is distributed on an "AS IS" BASIS, 11 + // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 + // See the License for the specific language governing permissions and 13 + // limitations under the License. 14 + 15 + package format 16 + 17 + // TODO: port more of the tests of go/printer 18 + 19 + import ( 20 + "bytes" 21 + "errors" 22 + "flag" 23 + "fmt" 24 + "io" 25 + "io/ioutil" 26 + "path/filepath" 27 + "testing" 28 + "time" 29 + 30 + "cuelang.org/go/cue/ast" 31 + "cuelang.org/go/cue/parser" 32 + "cuelang.org/go/cue/token" 33 + ) 34 + 35 + var ( 36 + defaultConfig = newConfig([]Option{FileSet(fset)}) 37 + Fprint = defaultConfig.fprint 38 + ) 39 + 40 + const ( 41 + dataDir = "testdata" 42 + tabwidth = 8 43 + ) 44 + 45 + var update = flag.Bool("update", false, "update golden files") 46 + 47 + var fset = token.NewFileSet() 48 + 49 + type checkMode uint 50 + 51 + const ( 52 + export checkMode = 1 << iota 53 + rawFormat 54 + idempotent 55 + simplify 56 + ) 57 + 58 + // format parses src, prints the corresponding AST, verifies the resulting 59 + // src is syntactically correct, and returns the resulting src or an error 60 + // if any. 61 + func format(src []byte, mode checkMode) ([]byte, error) { 62 + // parse src 63 + var opts []Option 64 + if mode&simplify != 0 { 65 + opts = append(opts, Simplify()) 66 + } 67 + 68 + res, err := Source(src, opts...) 69 + if err != nil { 70 + return nil, err 71 + } 72 + 73 + // make sure formatted output is syntactically correct 74 + if _, err := parser.ParseFile(fset, "", res, 75 + parser.ParseLambdas, parser.AllErrors); err != nil { 76 + return nil, fmt.Errorf("re-parse: %s\n%s", err, res) 77 + } 78 + 79 + return res, nil 80 + } 81 + 82 + // lineAt returns the line in text starting at offset offs. 83 + func lineAt(text []byte, offs int) []byte { 84 + i := offs 85 + for i < len(text) && text[i] != '\n' { 86 + i++ 87 + } 88 + return text[offs:i] 89 + } 90 + 91 + // diff compares a and b. 92 + func diff(aname, bname string, a, b []byte) error { 93 + var buf bytes.Buffer // holding long error message 94 + 95 + // compare lengths 96 + if len(a) != len(b) { 97 + fmt.Fprintf(&buf, "\nlength changed: len(%s) = %d, len(%s) = %d", aname, len(a), bname, len(b)) 98 + } 99 + 100 + // compare contents 101 + line := 1 102 + offs := 1 103 + for i := 0; i < len(a) && i < len(b); i++ { 104 + ch := a[i] 105 + if ch != b[i] { 106 + fmt.Fprintf(&buf, "\n%s:%d:%d: %s", aname, line, i-offs+1, lineAt(a, offs)) 107 + fmt.Fprintf(&buf, "\n%s:%d:%d: %s", bname, line, i-offs+1, lineAt(b, offs)) 108 + fmt.Fprintf(&buf, "\n\n") 109 + break 110 + } 111 + if ch == '\n' { 112 + line++ 113 + offs = i + 1 114 + } 115 + } 116 + 117 + if buf.Len() > 0 { 118 + return errors.New(buf.String()) 119 + } 120 + return nil 121 + } 122 + 123 + func runcheck(t *testing.T, source, golden string, mode checkMode) { 124 + src, err := ioutil.ReadFile(source) 125 + if err != nil { 126 + t.Error(err) 127 + return 128 + } 129 + 130 + res, err := format(src, mode) 131 + if err != nil { 132 + t.Error(err) 133 + return 134 + } 135 + 136 + // update golden files if necessary 137 + if *update { 138 + if err := ioutil.WriteFile(golden, res, 0644); err != nil { 139 + t.Error(err) 140 + } 141 + return 142 + } 143 + 144 + // get golden 145 + gld, err := ioutil.ReadFile(golden) 146 + if err != nil { 147 + t.Error(err) 148 + return 149 + } 150 + 151 + // formatted source and golden must be the same 152 + if err := diff(source, golden, res, gld); err != nil { 153 + t.Error(err) 154 + return 155 + } 156 + 157 + if mode&idempotent != 0 { 158 + // formatting golden must be idempotent 159 + // (This is very difficult to achieve in general and for now 160 + // it is only checked for files explicitly marked as such.) 161 + res, err = format(gld, mode) 162 + if err := diff(golden, fmt.Sprintf("format(%s)", golden), gld, res); err != nil { 163 + t.Errorf("golden is not idempotent: %s", err) 164 + } 165 + } 166 + } 167 + 168 + func check(t *testing.T, source, golden string, mode checkMode) { 169 + // run the test 170 + cc := make(chan int) 171 + go func() { 172 + runcheck(t, source, golden, mode) 173 + cc <- 0 174 + }() 175 + 176 + // wait with timeout 177 + select { 178 + case <-time.After(100000 * time.Second): // plenty of a safety margin, even for very slow machines 179 + // test running past time out 180 + t.Errorf("%s: running too slowly", source) 181 + case <-cc: 182 + // test finished within allotted time margin 183 + } 184 + } 185 + 186 + type entry struct { 187 + source, golden string 188 + mode checkMode 189 + } 190 + 191 + // Use go test -update to create/update the respective golden files. 192 + var data = []entry{ 193 + {"comments.input", "comments.golden", 0}, 194 + {"simplify.input", "simplify.golden", simplify}, 195 + {"expressions.input", "expressions.golden", 0}, 196 + } 197 + 198 + func TestFiles(t *testing.T) { 199 + t.Parallel() 200 + for _, e := range data { 201 + source := filepath.Join(dataDir, e.source) 202 + golden := filepath.Join(dataDir, e.golden) 203 + mode := e.mode 204 + t.Run(e.source, func(t *testing.T) { 205 + t.Parallel() 206 + check(t, source, golden, mode) 207 + // TODO(gri) check that golden is idempotent 208 + //check(t, golden, golden, e.mode) 209 + }) 210 + } 211 + } 212 + 213 + // Verify that the printer can be invoked during initialization. 214 + func init() { 215 + const name = "foobar" 216 + var buf bytes.Buffer 217 + if err := Fprint(&buf, &ast.Ident{Name: name}); err != nil { 218 + panic(err) // error in test 219 + } 220 + // in debug mode, the result contains additional information; 221 + // ignore it 222 + if s := buf.String(); !debug && s != name { 223 + panic("got " + s + ", want " + name) 224 + } 225 + } 226 + 227 + // Verify that the printer doesn't crash if the AST contains BadXXX nodes. 228 + func TestBadNodes(t *testing.T) { 229 + const src = "package p\n(" 230 + const res = "package p\n\n(BadExpr)\n" 231 + f, err := parser.ParseFile(fset, "", src, parser.ParseComments) 232 + if err == nil { 233 + t.Error("expected illegal program") // error in test 234 + } 235 + var buf bytes.Buffer 236 + Fprint(&buf, f) 237 + if buf.String() != res { 238 + t.Errorf("got %q, expected %q", buf.String(), res) 239 + } 240 + } 241 + 242 + // idents is an iterator that returns all idents in f via the result channel. 243 + func idents(f *ast.File) <-chan *ast.Ident { 244 + v := make(chan *ast.Ident) 245 + go func() { 246 + ast.Walk(f, func(n ast.Node) bool { 247 + if ident, ok := n.(*ast.Ident); ok { 248 + v <- ident 249 + } 250 + return true 251 + }, nil) 252 + close(v) 253 + }() 254 + return v 255 + } 256 + 257 + // identCount returns the number of identifiers found in f. 258 + func identCount(f *ast.File) int { 259 + n := 0 260 + for range idents(f) { 261 + n++ 262 + } 263 + return n 264 + } 265 + 266 + // Verify that the SourcePos mode emits correct //line comments 267 + // by testing that position information for matching identifiers 268 + // is maintained. 269 + func TestSourcePos(t *testing.T) { 270 + const src = `package p 271 + 272 + import ( 273 + "go/printer" 274 + "math" 275 + "regexp" 276 + ) 277 + 278 + pi = 3.14 // TODO: allow on same line 279 + xx = 0 280 + t: { 281 + x: int 282 + y: int 283 + z: int 284 + u: number 285 + v: number 286 + w: number 287 + } 288 + e: a*t.x + b*t.y 289 + 290 + // two extra lines here // ... 291 + e2: c*t.z 292 + ` 293 + 294 + // parse original 295 + f1, err := parser.ParseFile(fset, "src", src, parser.ParseComments) 296 + if err != nil { 297 + t.Fatal(err) 298 + } 299 + 300 + // pretty-print original 301 + var buf bytes.Buffer 302 + err = (&config{UseSpaces: true, Tabwidth: 8}).fprint(&buf, f1) 303 + if err != nil { 304 + t.Fatal(err) 305 + } 306 + 307 + // parse pretty printed original 308 + // (//line comments must be interpreted even w/o syntax.ParseComments set) 309 + f2, err := parser.ParseFile(fset, "", buf.Bytes(), 310 + parser.AllErrors, parser.ParseLambdas, parser.ParseComments) 311 + if err != nil { 312 + t.Fatalf("%s\n%s", err, buf.Bytes()) 313 + } 314 + 315 + // At this point the position information of identifiers in f2 should 316 + // match the position information of corresponding identifiers in f1. 317 + 318 + // number of identifiers must be > 0 (test should run) and must match 319 + n1 := identCount(f1) 320 + n2 := identCount(f2) 321 + if n1 == 0 { 322 + t.Fatal("got no idents") 323 + } 324 + if n2 != n1 { 325 + t.Errorf("got %d idents; want %d", n2, n1) 326 + } 327 + 328 + // verify that all identifiers have correct line information 329 + i2range := idents(f2) 330 + for i1 := range idents(f1) { 331 + i2 := <-i2range 332 + 333 + if i2 == nil || i1 == nil { 334 + t.Fatal("non nil identifiers") 335 + } 336 + if i2.Name != i1.Name { 337 + t.Errorf("got ident %s; want %s", i2.Name, i1.Name) 338 + } 339 + 340 + l1 := fset.Position(i1.Pos()).Line 341 + l2 := fset.Position(i2.Pos()).Line 342 + if l2 != l1 { 343 + t.Errorf("got line %d; want %d for %s", l2, l1, i1.Name) 344 + } 345 + } 346 + 347 + if t.Failed() { 348 + t.Logf("\n%s", buf.Bytes()) 349 + } 350 + } 351 + 352 + var decls = []string{ 353 + `import "fmt"`, 354 + "pi = 3.1415\ne = 2.71828\n\nx = pi", 355 + } 356 + 357 + func TestDeclLists(t *testing.T) { 358 + for _, src := range decls { 359 + file, err := parser.ParseFile(fset, "", "package p\n"+src, parser.ParseComments) 360 + if err != nil { 361 + panic(err) // error in test 362 + } 363 + 364 + var buf bytes.Buffer 365 + err = Fprint(&buf, file.Decls) // only print declarations 366 + if err != nil { 367 + panic(err) // error in test 368 + } 369 + 370 + out := buf.String() 371 + 372 + if out != src { 373 + t.Errorf("\ngot : %q\nwant: %q\n", out, src) 374 + } 375 + } 376 + } 377 + 378 + var stmts = []string{ 379 + "i := 0", 380 + "select {}\nvar a, b = 1, 2\nreturn a + b", 381 + "go f()\ndefer func() {}()", 382 + } 383 + 384 + type limitWriter struct { 385 + remaining int 386 + errCount int 387 + } 388 + 389 + func (l *limitWriter) Write(buf []byte) (n int, err error) { 390 + n = len(buf) 391 + if n >= l.remaining { 392 + n = l.remaining 393 + err = io.EOF 394 + l.errCount++ 395 + } 396 + l.remaining -= n 397 + return n, err 398 + } 399 + 400 + // TextX is a skeleton test that can be filled in for debugging one-off cases. 401 + // Do not remove. 402 + func TestX(t *testing.T) { 403 + const src = ` 404 + { e: k <- 405 + for a, v in s} 406 + a: b 407 + 408 + ` 409 + b, err := format([]byte(src), 0) 410 + if err != nil { 411 + t.Error(err) 412 + } 413 + _ = b 414 + // t.Error("\n", string(b)) 415 + }
+688
cue/format/node.go
··· 1 + // Copyright 2018 The CUE Authors 2 + // 3 + // Licensed under the Apache License, Version 2.0 (the "License"); 4 + // you may not use this file except in compliance with the License. 5 + // You may obtain a copy of the License at 6 + // 7 + // http://www.apache.org/licenses/LICENSE-2.0 8 + // 9 + // Unless required by applicable law or agreed to in writing, software 10 + // distributed under the License is distributed on an "AS IS" BASIS, 11 + // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 + // See the License for the specific language governing permissions and 13 + // limitations under the License. 14 + 15 + package format 16 + 17 + import ( 18 + "fmt" 19 + "strconv" 20 + "strings" 21 + 22 + "cuelang.org/go/cue/ast" 23 + "cuelang.org/go/cue/parser" 24 + "cuelang.org/go/cue/token" 25 + ) 26 + 27 + func printNode(node interface{}, f *printer) error { 28 + s := newFormatter(f) 29 + 30 + // format node 31 + f.allowed = nooverride // gobble initial whitespace. 32 + switch x := node.(type) { 33 + case *ast.File: 34 + s.file(x) 35 + case ast.Expr: 36 + s.expr(x) 37 + case ast.Decl: 38 + s.decl(x) 39 + // case ast.Node: // TODO: do we need this? 40 + // s.walk(x) 41 + case []ast.Decl: 42 + s.walkDeclList(x) 43 + default: 44 + goto unsupported 45 + } 46 + 47 + return nil 48 + 49 + unsupported: 50 + return fmt.Errorf("cue/format: unsupported node type %T", node) 51 + } 52 + 53 + // Helper functions for common node lists. They may be empty. 54 + 55 + func (f *formatter) walkDeclList(list []ast.Decl) { 56 + f.before(nil) 57 + for i, x := range list { 58 + if i > 0 { 59 + f.print(declcomma) 60 + } 61 + f.decl(x) 62 + f.print(f.current.parentSep) 63 + } 64 + f.after(nil) 65 + } 66 + 67 + func (f *formatter) walkSpecList(list []*ast.ImportSpec) { 68 + f.before(nil) 69 + for _, x := range list { 70 + f.importSpec(x) 71 + } 72 + f.after(nil) 73 + } 74 + 75 + func (f *formatter) walkClauseList(list []ast.Clause) { 76 + f.before(nil) 77 + for _, x := range list { 78 + f.clause(x) 79 + } 80 + f.after(nil) 81 + } 82 + 83 + func (f *formatter) walkExprList(list []ast.Expr, depth int) { 84 + f.before(nil) 85 + for _, x := range list { 86 + f.before(x) 87 + f.exprRaw(x, token.LowestPrec, depth) 88 + f.print(comma, blank) 89 + f.after(x) 90 + } 91 + f.after(nil) 92 + } 93 + 94 + func (f *formatter) file(file *ast.File) { 95 + f.before(file) 96 + if file.Name != nil { 97 + f.print(file.Package, "package") 98 + f.print(blank, file.Name, newline, newsection) 99 + } 100 + f.current.pos = 3 101 + f.visitComments(3) 102 + f.walkDeclList(file.Decls) 103 + f.after(file) 104 + f.print(token.EOF) 105 + } 106 + func (f *formatter) decl(decl ast.Decl) { 107 + if decl == nil { 108 + return 109 + } 110 + if !f.before(decl) { 111 + goto after 112 + } 113 + switch n := decl.(type) { 114 + case *ast.Field: 115 + // shortcut single-element structs. 116 + lastSize := len(f.labelBuf) 117 + f.labelBuf = f.labelBuf[:0] 118 + first := n.Label 119 + for { 120 + obj, ok := n.Value.(*ast.StructLit) 121 + if !ok || len(obj.Elts) != 1 || (obj.Lbrace.IsValid() && !f.printer.cfg.simplify) { 122 + break 123 + } 124 + 125 + // Verify that struct doesn't have inside comments and that 126 + // element doesn't have doc comments. 127 + hasComments := len(obj.Elts[0].Comments()) > 0 128 + for _, c := range obj.Comments() { 129 + if c.Position == 1 || c.Position == 2 { 130 + hasComments = true 131 + } 132 + } 133 + if hasComments { 134 + break 135 + } 136 + 137 + mem, ok := obj.Elts[0].(*ast.Field) 138 + if !ok { 139 + break 140 + } 141 + f.labelBuf = append(f.labelBuf, mem.Label) 142 + n = mem 143 + } 144 + 145 + if lastSize != len(f.labelBuf) { 146 + f.print(formfeed) 147 + } 148 + 149 + f.before(nil) 150 + f.label(first) 151 + for _, x := range f.labelBuf { 152 + f.print(blank, nooverride) 153 + f.label(x) 154 + } 155 + f.after(nil) 156 + 157 + nextFF := f.nextNeedsFormfeed(n.Value) 158 + tab := vtab 159 + if nextFF || f.nestExpr >= 1 { 160 + tab = blank 161 + } 162 + 163 + f.print(n.Colon, token.COLON, tab) 164 + if n.Value != nil { 165 + f.expr(n.Value) 166 + } else { 167 + f.current.pos++ 168 + f.visitComments(f.current.pos) 169 + } 170 + 171 + if nextFF { 172 + f.print(formfeed) 173 + } 174 + 175 + case *ast.ComprehensionDecl: 176 + f.decl(n.Field) 177 + f.print(blank) 178 + if n.Select != token.NoPos { 179 + f.print(n.Select, token.ARROW, blank) 180 + } 181 + f.print(indent) 182 + f.walkClauseList(n.Clauses) 183 + f.print(unindent) 184 + f.print("") // force whitespace to be written 185 + 186 + case *ast.BadDecl: 187 + f.print(n.From, "*bad decl*", declcomma) 188 + 189 + case *ast.ImportDecl: 190 + f.print(n.Import, "import") 191 + if len(n.Specs) == 0 { 192 + f.print(blank, n.Lparen, token.LPAREN, n.Rparen, token.RPAREN, newline) 193 + } 194 + switch { 195 + case len(n.Specs) == 1: 196 + if !n.Lparen.IsValid() { 197 + f.print(blank) 198 + f.walkSpecList(n.Specs) 199 + break 200 + } 201 + fallthrough 202 + default: 203 + f.print(blank, n.Lparen, token.LPAREN, newline, indent) 204 + f.walkSpecList(n.Specs) 205 + f.print(unindent, newline, n.Rparen, token.RPAREN, newline) 206 + } 207 + f.print(newsection) 208 + 209 + case *ast.EmitDecl: 210 + f.expr(n.Expr) 211 + f.print(newline, newsection) // force newline 212 + 213 + case *ast.Alias: 214 + f.expr(n.Ident) 215 + f.print(blank, n.Equal, token.BIND, blank) 216 + f.expr(n.Expr) 217 + f.print(declcomma, newline) // implied 218 + } 219 + after: 220 + f.after(decl) 221 + } 222 + 223 + func (f *formatter) nextNeedsFormfeed(n ast.Expr) bool { 224 + switch x := n.(type) { 225 + case *ast.StructLit: 226 + return true 227 + case *ast.BasicLit: 228 + return strings.IndexByte(x.Value, '\n') >= 0 229 + case *ast.ListLit: 230 + return true 231 + } 232 + return false 233 + } 234 + 235 + func (f *formatter) importSpec(x *ast.ImportSpec) { 236 + if x.Name != nil { 237 + f.label(x.Name) 238 + f.print(blank) 239 + } else { 240 + f.current.pos++ 241 + f.visitComments(f.current.pos) 242 + } 243 + f.expr(x.Path) 244 + f.print(newline) 245 + } 246 + 247 + func (f *formatter) label(l ast.Label) { 248 + switch n := l.(type) { 249 + case *ast.Ident: 250 + f.print(n.NamePos, n) 251 + 252 + case *ast.BasicLit: 253 + if f.cfg.simplify && n.Kind == token.STRING && len(n.Value) > 2 { 254 + s := n.Value 255 + unquoted, err := strconv.Unquote(s) 256 + fset := token.NewFileSet() 257 + if err == nil { 258 + e, _ := parser.ParseExpr(fset, "check", unquoted) 259 + if _, ok := e.(*ast.Ident); ok { 260 + f.print(n.ValuePos, unquoted) 261 + break 262 + } 263 + } 264 + } 265 + f.print(n.ValuePos, n.Value) 266 + 267 + case *ast.TemplateLabel: 268 + f.print(n.Langle, token.LSS, indent) 269 + f.label(n.Ident) 270 + f.print(unindent, n.Rangle, token.GTR) 271 + 272 + case *ast.Interpolation: 273 + f.expr(n) 274 + 275 + case *ast.ExprLabel: 276 + f.print(n.Lbrack, token.LBRACK, indent) 277 + f.expr(n.Label) 278 + f.print(unindent, n.Rbrack, token.RBRACK) 279 + 280 + default: 281 + panic(fmt.Sprintf("unknown label type %T", n)) 282 + } 283 + } 284 + 285 + func (f *formatter) expr(x ast.Expr) { 286 + const depth = 1 287 + f.expr1(x, token.LowestPrec, depth) 288 + } 289 + 290 + func (f *formatter) expr0(x ast.Expr, depth int) { 291 + f.expr1(x, token.LowestPrec, depth) 292 + } 293 + 294 + func (f *formatter) expr1(expr ast.Expr, prec1, depth int) { 295 + if f.before(expr) { 296 + f.exprRaw(expr, prec1, depth) 297 + } 298 + f.after(expr) 299 + } 300 + 301 + func (f *formatter) exprRaw(expr ast.Expr, prec1, depth int) { 302 + 303 + switch x := expr.(type) { 304 + case *ast.BadExpr: 305 + f.print(x.From, "BadExpr") 306 + 307 + case *ast.BottomLit: 308 + f.print(x.Bottom, token.BOTTOM) 309 + 310 + case *ast.Ident: 311 + f.print(x.NamePos, x) 312 + 313 + case *ast.BinaryExpr: 314 + if depth < 1 { 315 + f.internalError("depth < 1:", depth) 316 + depth = 1 317 + } 318 + if prec1 == 8 { // .. 319 + prec1 = 9 // always parentheses for values of ranges 320 + } 321 + f.binaryExpr(x, prec1, cutoff(x, depth), depth) 322 + 323 + case *ast.UnaryExpr: 324 + const prec = token.UnaryPrec 325 + if prec < prec1 { 326 + // parenthesis needed 327 + f.print(token.LPAREN, nooverride) 328 + f.expr(x) 329 + f.print(token.RPAREN) 330 + } else { 331 + // no parenthesis needed 332 + f.print(x.OpPos, x.Op, nooverride) 333 + f.expr1(x.X, prec, depth) 334 + } 335 + 336 + case *ast.BasicLit: 337 + f.print(x.ValuePos, x) 338 + 339 + case *ast.Interpolation: 340 + f.before(nil) 341 + for _, x := range x.Elts { 342 + f.expr0(x, depth+1) 343 + } 344 + f.after(nil) 345 + 346 + case *ast.ParenExpr: 347 + if _, hasParens := x.X.(*ast.ParenExpr); hasParens { 348 + // don't print parentheses around an already parenthesized expression 349 + // TODO: consider making this more general and incorporate precedence levels 350 + f.expr0(x.X, depth) 351 + } else { 352 + f.print(x.Lparen, token.LPAREN) 353 + f.expr0(x.X, reduceDepth(depth)) // parentheses undo one level of depth 354 + f.print(x.Rparen, token.RPAREN) 355 + } 356 + 357 + case *ast.SelectorExpr: 358 + f.selectorExpr(x, depth) 359 + 360 + case *ast.IndexExpr: 361 + f.expr1(x.X, token.HighestPrec, 1) 362 + f.print(x.Lbrack, token.LBRACK) 363 + f.expr0(x.Index, depth+1) 364 + f.print(x.Rbrack, token.RBRACK) 365 + 366 + case *ast.SliceExpr: 367 + f.expr1(x.X, token.HighestPrec, 1) 368 + f.print(x.Lbrack, token.LBRACK) 369 + indices := []ast.Expr{x.Low, x.High} 370 + for i, y := range indices { 371 + if i > 0 { 372 + // blanks around ":" if both sides exist and either side is a binary expression 373 + x := indices[i-1] 374 + if depth <= 1 && x != nil && y != nil && (isBinary(x) || isBinary(y)) { 375 + f.print(blank, token.COLON, blank) 376 + } else { 377 + f.print(token.COLON) 378 + } 379 + } 380 + if y != nil { 381 + f.expr0(y, depth+1) 382 + } 383 + } 384 + f.print(x.Rbrack, token.RBRACK) 385 + 386 + case *ast.CallExpr: 387 + if len(x.Args) > 1 { 388 + depth++ 389 + } 390 + wasIndented := f.possibleSelectorExpr(x.Fun, token.HighestPrec, depth) 391 + f.print(x.Lparen, token.LPAREN) 392 + f.walkExprList(x.Args, depth) 393 + f.print(trailcomma, noblank, x.Rparen, token.RPAREN) 394 + if wasIndented { 395 + f.print(unindent) 396 + } 397 + 398 + case *ast.Ellipsis: 399 + f.print(x.Ellipsis, token.ELLIPSIS) 400 + if x.Elt != nil { 401 + f.expr(x.Elt) // TODO 402 + } 403 + 404 + case *ast.StructLit: 405 + f.print(x.Lbrace, token.LBRACE, noblank, f.formfeed(), indent) 406 + f.walkDeclList(x.Elts) 407 + f.matchUnindent() 408 + f.print(noblank, x.Rbrace, token.RBRACE) 409 + 410 + case *ast.ListLit: 411 + f.print(x.Lbrack, token.LBRACK, indent) 412 + f.walkExprList(x.Elts, 1) 413 + if x.Ellipsis != token.NoPos || x.Type != nil { 414 + f.print(x.Ellipsis, token.ELLIPSIS) 415 + if x.Type != nil && !isTop(x.Type) { 416 + f.expr(x.Type) 417 + } 418 + } else { 419 + f.print(trailcomma, noblank) 420 + f.current.pos += 2 421 + f.visitComments(f.current.pos) 422 + } 423 + f.matchUnindent() 424 + f.print(noblank, x.Rbrack, token.RBRACK) 425 + 426 + case *ast.ListComprehension: 427 + f.print(x.Lbrack, token.LBRACK, blank, indent) 428 + f.expr(x.Expr) 429 + f.print(blank) 430 + f.walkClauseList(x.Clauses) 431 + f.print(unindent, f.wsOverride(blank), x.Rbrack, token.RBRACK) 432 + 433 + case *ast.LambdaExpr: 434 + f.print(x.Lparen, token.LPAREN, indent, noblank) 435 + 436 + f.before(nil) 437 + for _, x := range x.Params { 438 + f.label(x.Label) 439 + if x.Colon.IsValid() { 440 + f.print(x.Colon, token.COLON, blank) 441 + f.expr(x.Value) 442 + } 443 + f.print(comma, blank) 444 + } 445 + f.print(trailcomma, noblank) 446 + f.after(nil) 447 + 448 + f.print(trailcomma, noblank, unindent) 449 + f.print(x.Rparen, token.RPAREN, blank) 450 + f.print(token.LAMBDA, blank) 451 + f.expr(x.Expr) 452 + 453 + default: 454 + panic(fmt.Sprintf("unimplemented type %T", x)) 455 + } 456 + return 457 + } 458 + 459 + func (f *formatter) clause(clause ast.Clause) { 460 + switch n := clause.(type) { 461 + case *ast.ForClause: 462 + f.print(blank, n.For, "for", blank) 463 + if n.Key != nil { 464 + f.label(n.Key) 465 + f.print(n.Colon, token.COMMA, blank) 466 + } else { 467 + f.current.pos++ 468 + f.visitComments(f.current.pos) 469 + } 470 + f.label(n.Value) 471 + f.print(blank, n.In, "in", blank) 472 + f.expr(n.Source) 473 + 474 + case *ast.IfClause: 475 + f.print(blank, n.If, "if", blank) 476 + f.expr(n.Condition) 477 + 478 + default: 479 + panic("unknown clause type") 480 + } 481 + } 482 + 483 + func walkBinary(e *ast.BinaryExpr) (has6, has7, has8 bool, maxProblem int) { 484 + switch e.Op.Precedence() { 485 + case 6: 486 + has6 = true 487 + case 7: 488 + has7 = true 489 + case 8: 490 + has8 = true 491 + } 492 + 493 + switch l := e.X.(type) { 494 + case *ast.BinaryExpr: 495 + if l.Op.Precedence() < e.Op.Precedence() { 496 + // parens will be inserted. 497 + // pretend this is an *syntax.ParenExpr and do nothing. 498 + break 499 + } 500 + h6, h7, h8, mp := walkBinary(l) 501 + has6 = has6 || h6 502 + has7 = has7 || h7 503 + has8 = has8 || h8 504 + if maxProblem < mp { 505 + maxProblem = mp 506 + } 507 + } 508 + 509 + switch r := e.Y.(type) { 510 + case *ast.BinaryExpr: 511 + if r.Op.Precedence() <= e.Op.Precedence() { 512 + // parens will be inserted. 513 + // pretend this is an *syntax.ParenExpr and do nothing. 514 + break 515 + } 516 + h6, h7, h8, mp := walkBinary(r) 517 + has6 = has6 || h6 518 + has7 = has7 || h7 519 + has8 = has8 || h8 520 + if maxProblem < mp { 521 + maxProblem = mp 522 + } 523 + 524 + case *ast.UnaryExpr: 525 + switch e.Op.String() + r.Op.String() { 526 + case "/*": 527 + maxProblem = 8 528 + case "++", "--": 529 + if maxProblem < 6 { 530 + maxProblem = 6 531 + } 532 + } 533 + } 534 + return 535 + } 536 + 537 + func cutoff(e *ast.BinaryExpr, depth int) int { 538 + has6, has7, has8, maxProblem := walkBinary(e) 539 + if maxProblem > 0 { 540 + return maxProblem + 1 541 + } 542 + if (has6 || has7) && has8 { 543 + if depth == 1 { 544 + return 8 545 + } 546 + if has7 { 547 + return 7 548 + } 549 + return 6 550 + } 551 + if has6 && has7 { 552 + if depth == 1 { 553 + return 7 554 + } 555 + return 6 556 + } 557 + if depth == 1 { 558 + return 8 559 + } 560 + return 6 561 + } 562 + 563 + func diffPrec(expr ast.Expr, prec int) int { 564 + x, ok := expr.(*ast.BinaryExpr) 565 + if !ok || prec != x.Op.Precedence() { 566 + return 1 567 + } 568 + return 0 569 + } 570 + 571 + func reduceDepth(depth int) int { 572 + depth-- 573 + if depth < 1 { 574 + depth = 1 575 + } 576 + return depth 577 + } 578 + 579 + // Format the binary expression: decide the cutoff and then format. 580 + // Let's call depth == 1 Normal mode, and depth > 1 Compact mode. 581 + // (Algorithm suggestion by Russ Cox.) 582 + // 583 + // The precedences are: 584 + // 8 .. 585 + // 7 * / % quo rem div mod 586 + // 6 + - 587 + // 5 == != < <= > >= 588 + // 4 && 589 + // 3 || 590 + // 2 & 591 + // 1 | 592 + // 593 + // The only decision is whether there will be spaces around levels 6 and 7. 594 + // There are never spaces at level 8 (unary), and always spaces at levels 5 and below. 595 + // 596 + // To choose the cutoff, look at the whole expression but excluding primary 597 + // expressions (function calls, parenthesized exprs), and apply these rules: 598 + // 599 + // 1) If there is a binary operator with a right side unary operand 600 + // that would clash without a space, the cutoff must be (in order): 601 + // 602 + // /* 8 603 + // ++ 7 // not necessary, but to avoid confusion 604 + // -- 7 605 + // 606 + // (Comparison operators always have spaces around them.) 607 + // 608 + // 2) If there is a mix of level 7 and level 6 operators, then the cutoff 609 + // is 7 (use spaces to distinguish precedence) in Normal mode 610 + // and 6 (never use spaces) in Compact mode. 611 + // 612 + // 3) If there are no level 6 operators or no level 7 operators, then the 613 + // cutoff is 8 (always use spaces) in Normal mode 614 + // and 6 (never use spaces) in Compact mode. 615 + // 616 + func (f *formatter) binaryExpr(x *ast.BinaryExpr, prec1, cutoff, depth int) { 617 + f.nestExpr++ 618 + defer func() { f.nestExpr-- }() 619 + 620 + prec := x.Op.Precedence() 621 + if prec < prec1 { 622 + // parenthesis needed 623 + // Note: The parser inserts an syntax.ParenExpr node; thus this case 624 + // can only occur if the AST is created in a different way. 625 + // defer p.pushComment(nil).pop() 626 + f.print(token.LPAREN, nooverride) 627 + f.expr0(x, reduceDepth(depth)) // parentheses undo one level of depth 628 + f.print(token.RPAREN) 629 + return 630 + } 631 + 632 + printBlank := prec < cutoff 633 + 634 + ws := indent 635 + f.expr1(x.X, prec, depth+diffPrec(x.X, prec)) 636 + f.print(nooverride) 637 + if printBlank { 638 + f.print(blank) 639 + } 640 + f.print(x.OpPos, x.Op) 641 + if x.Y.Pos().IsNewline() { 642 + // at least one line break, but respect an extra empty line 643 + // in the source 644 + f.print(formfeed) 645 + printBlank = false // no blank after line break 646 + } else { 647 + f.print(nooverride) 648 + } 649 + if printBlank { 650 + f.print(blank) 651 + } 652 + f.expr1(x.Y, prec+1, depth+1) 653 + if ws == ignore { 654 + f.print(unindent) 655 + } 656 + } 657 + 658 + func isBinary(expr ast.Expr) bool { 659 + _, ok := expr.(*ast.BinaryExpr) 660 + return ok 661 + } 662 + 663 + func (f *formatter) possibleSelectorExpr(expr ast.Expr, prec1, depth int) bool { 664 + if x, ok := expr.(*ast.SelectorExpr); ok { 665 + return f.selectorExpr(x, depth) 666 + } 667 + f.expr1(expr, prec1, depth) 668 + return false 669 + } 670 + 671 + // selectorExpr handles an *syntax.SelectorExpr node and returns whether x spans 672 + // multiple lines. 673 + func (f *formatter) selectorExpr(x *ast.SelectorExpr, depth int) bool { 674 + f.expr1(x.X, token.HighestPrec, depth) 675 + f.print(token.PERIOD) 676 + if x.Sel.Pos().IsNewline() { 677 + f.print(indent, formfeed, x.Sel.Pos(), x.Sel) 678 + f.print(unindent) 679 + return true 680 + } 681 + f.print(x.Sel.Pos(), x.Sel) 682 + return false 683 + } 684 + 685 + func isTop(e ast.Expr) bool { 686 + ident, ok := e.(*ast.Ident) 687 + return ok && ident.Name == "_" 688 + }
+369
cue/format/printer.go
··· 1 + // Copyright 2018 The CUE Authors 2 + // 3 + // Licensed under the Apache License, Version 2.0 (the "License"); 4 + // you may not use this file except in compliance with the License. 5 + // You may obtain a copy of the License at 6 + // 7 + // http://www.apache.org/licenses/LICENSE-2.0 8 + // 9 + // Unless required by applicable law or agreed to in writing, software 10 + // distributed under the License is distributed on an "AS IS" BASIS, 11 + // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 + // See the License for the specific language governing permissions and 13 + // limitations under the License. 14 + 15 + package format 16 + 17 + import ( 18 + "fmt" 19 + "os" 20 + "text/tabwriter" 21 + 22 + "cuelang.org/go/cue/ast" 23 + "cuelang.org/go/cue/token" 24 + ) 25 + 26 + // A printer takes the stream of formatting tokens and spacing directives 27 + // produced by the formatter and adjusts the spacing based on the original 28 + // source code. 29 + type printer struct { 30 + w tabwriter.Writer 31 + 32 + fset *token.FileSet 33 + cfg *config 34 + 35 + allowed whiteSpace 36 + requested whiteSpace 37 + indentStack []whiteSpace 38 + indentPos int 39 + 40 + pos token.Position // current pos in AST 41 + 42 + lastTok token.Token // last token printed (syntax.ILLEGAL if it's whitespace) 43 + 44 + output []byte 45 + indent int 46 + spaceBefore bool 47 + } 48 + 49 + func (p *printer) init(cfg *config) { 50 + p.cfg = cfg 51 + p.pos = token.Position{Line: 1, Column: 1} 52 + } 53 + 54 + const debug = false 55 + 56 + func (p *printer) internalError(msg ...interface{}) { 57 + if debug { 58 + fmt.Print(p.pos.String() + ": ") 59 + fmt.Println(msg...) 60 + panic("go/printer") 61 + } 62 + } 63 + 64 + func (p *printer) lineFor(pos token.Pos) int { 65 + if p.fset == nil { 66 + return 0 67 + } 68 + return p.fset.Position(pos).Line 69 + } 70 + 71 + func (p *printer) Print(v interface{}) { 72 + var ( 73 + impliedComma = false 74 + isLit bool 75 + data string 76 + nextWS whiteSpace 77 + ) 78 + switch x := v.(type) { 79 + case token.Token: 80 + s := x.String() 81 + before, after := mayCombine(p.lastTok, x) 82 + if before && !p.spaceBefore { 83 + // the previous and the current token must be 84 + // separated by a blank otherwise they combine 85 + // into a different incorrect token sequence 86 + // (except for syntax.INT followed by a '.' this 87 + // should never happen because it is taken care 88 + // of via binary expression formatting) 89 + if p.allowed&blank != 0 { 90 + p.internalError("whitespace buffer not empty") 91 + } 92 + p.allowed |= blank 93 + } 94 + if after { 95 + nextWS = blank 96 + } 97 + data = s 98 + switch x { 99 + case token.EOF: 100 + data = "" 101 + p.allowed = newline 102 + p.allowed &^= newsection 103 + case token.LPAREN, token.LBRACK, token.LBRACE: 104 + case token.RPAREN, token.RBRACK, token.RBRACE: 105 + impliedComma = true 106 + } 107 + p.lastTok = x 108 + 109 + case *ast.BasicLit: 110 + data = x.Value 111 + isLit = true 112 + impliedComma = true 113 + p.lastTok = x.Kind 114 + 115 + case *ast.Ident: 116 + data = x.Name 117 + impliedComma = true 118 + p.lastTok = token.IDENT 119 + 120 + case string: 121 + data = x 122 + impliedComma = true 123 + p.lastTok = token.STRING 124 + 125 + case *ast.CommentGroup: 126 + rel := x.Pos().RelPos() 127 + if x.Line { // TODO: we probably don't need this. 128 + rel = token.Blank 129 + } 130 + switch rel { 131 + case token.NoRelPos: 132 + case token.Newline, token.NewSection: 133 + case token.Blank, token.Elided: 134 + p.allowed |= blank 135 + fallthrough 136 + case token.NoSpace: 137 + p.allowed &^= newline | newsection | formfeed | declcomma 138 + } 139 + return 140 + 141 + case *ast.Comment: 142 + // TODO: if implied comma, postpone comment 143 + data = x.Text 144 + p.lastTok = token.COMMENT 145 + break 146 + 147 + case whiteSpace: 148 + p.allowed |= x 149 + return 150 + 151 + case token.Pos: 152 + // TODO: should we use a known file position to synchronize? Go does, 153 + // but we don't really have to. 154 + // pos := p.fset.Position(x) 155 + if x.HasRelPos() { 156 + if p.allowed&nooverride == 0 { 157 + requested := p.allowed 158 + switch x.RelPos() { 159 + case token.NoSpace: 160 + requested &^= newline | newsection | formfeed 161 + case token.Blank: 162 + requested |= blank 163 + requested &^= newline | newsection | formfeed 164 + case token.Newline: 165 + requested |= newline 166 + case token.NewSection: 167 + requested |= newsection 168 + } 169 + p.writeWhitespace(requested) 170 + p.allowed = 0 171 + p.requested = 0 172 + } 173 + // p.pos = pos 174 + } 175 + return 176 + 177 + default: 178 + fmt.Fprintf(os.Stderr, "print: unsupported argument %v (%T)\n", x, x) 179 + panic("go/printer type") 180 + } 181 + 182 + p.writeWhitespace(p.allowed) 183 + p.allowed = 0 184 + p.requested = 0 185 + p.writeString(data, isLit) 186 + p.allowed = nextWS 187 + _ = impliedComma // TODO: delay comment printings 188 + } 189 + 190 + func (p *printer) writeWhitespace(ws whiteSpace) { 191 + if debug { 192 + p.output = append(p.output, fmt.Sprintf("/*=%x=*/", p.allowed)...) // do not update f.pos! 193 + } 194 + 195 + if ws&comma != 0 { 196 + switch { 197 + case ws&(newsection|newline|formfeed) != 0, 198 + ws&trailcomma == 0: 199 + p.writeByte(',', 1) 200 + } 201 + } 202 + if ws&indent != 0 { 203 + p.markLineIndent(ws) 204 + } 205 + if ws&unindent != 0 { 206 + p.markUnindentLine() 207 + } 208 + switch { 209 + case ws&newsection != 0: 210 + p.maybeIndentLine(ws) 211 + p.writeByte('\f', 2) 212 + p.spaceBefore = true 213 + case ws&formfeed != 0: 214 + p.maybeIndentLine(ws) 215 + p.writeByte('\f', 1) 216 + p.spaceBefore = true 217 + case ws&newline != 0: 218 + p.maybeIndentLine(ws) 219 + p.writeByte('\n', 1) 220 + p.spaceBefore = true 221 + case ws&declcomma != 0: 222 + p.writeByte(',', 1) 223 + p.writeByte(' ', 1) 224 + p.spaceBefore = true 225 + case ws&noblank != 0: 226 + case ws&vtab != 0: 227 + p.writeByte('\v', 1) 228 + p.spaceBefore = true 229 + case ws&blank != 0: 230 + p.writeByte(' ', 1) 231 + p.spaceBefore = true 232 + } 233 + } 234 + 235 + func (p *printer) markLineIndent(ws whiteSpace) { 236 + p.indentStack = append(p.indentStack, ws) 237 + } 238 + 239 + func (p *printer) markUnindentLine() (wasUnindented bool) { 240 + last := len(p.indentStack) - 1 241 + if ws := p.indentStack[last]; ws&indented != 0 { 242 + p.indent-- 243 + wasUnindented = true 244 + } 245 + p.indentStack = p.indentStack[:last] 246 + return wasUnindented 247 + } 248 + 249 + func (p *printer) maybeIndentLine(ws whiteSpace) { 250 + if ws&unindent == 0 && len(p.indentStack) > 0 { 251 + last := len(p.indentStack) - 1 252 + if ws := p.indentStack[last]; ws&indented != 0 || ws&indent == 0 { 253 + return 254 + } 255 + p.indentStack[last] |= indented 256 + p.indent++ 257 + } 258 + } 259 + 260 + func (f *formatter) matchUnindent() whiteSpace { 261 + f.allowed |= unindent 262 + // TODO: make this work. Whitespace from closing bracket should match that 263 + // of opening if there is no position information. 264 + // f.allowed &^= nooverride | newline | newsection | formfeed | blank | noblank 265 + // ws := f.indentStack[len(f.indentStack)-1] 266 + // mask := blank | noblank | vtab 267 + // f.allowed |= unindent | blank | noblank 268 + // if ws&newline != 0 || ws*indented != 0 { 269 + // f.allowed |= newline 270 + // } 271 + return 0 272 + } 273 + 274 + // writeString writes the string s to p.output and updates p.pos, p.out, 275 + // and p.last. If isLit is set, s is escaped w/ tabwriter.Escape characters 276 + // to protect s from being interpreted by the tabwriter. 277 + // 278 + // Note: writeString is only used to write Go tokens, literals, and 279 + // comments, all of which must be written literally. Thus, it is correct 280 + // to always set isLit = true. However, setting it explicitly only when 281 + // needed (i.e., when we don't know that s contains no tabs or line breaks) 282 + // avoids processing extra escape characters and reduces run time of the 283 + // printer benchmark by up to 10%. 284 + // 285 + func (p *printer) writeString(s string, isLit bool) { 286 + if s != "" { 287 + p.spaceBefore = false 288 + } 289 + 290 + if isLit { 291 + // Protect s such that is passes through the tabwriter 292 + // unchanged. Note that valid Go programs cannot contain 293 + // tabwriter.Escape bytes since they do not appear in legal 294 + // UTF-8 sequences. 295 + p.output = append(p.output, tabwriter.Escape) 296 + } 297 + 298 + if debug { 299 + p.output = append(p.output, fmt.Sprintf("/*%s*/", p.pos)...) // do not update f.pos! 300 + } 301 + p.output = append(p.output, s...) 302 + 303 + if isLit { 304 + p.output = append(p.output, tabwriter.Escape) 305 + } 306 + // update positions 307 + nLines := 0 308 + var li int // index of last newline; valid if nLines > 0 309 + for i := 0; i < len(s); i++ { 310 + // CUE tokens cannot contain '\f' - no need to look for it 311 + if s[i] == '\n' { 312 + nLines++ 313 + li = i 314 + } 315 + } 316 + p.pos.Offset += len(s) 317 + if nLines > 0 { 318 + p.pos.Line += nLines 319 + c := len(s) - li 320 + p.pos.Column = c 321 + } else { 322 + p.pos.Column += len(s) 323 + } 324 + } 325 + 326 + func (p *printer) writeByte(ch byte, n int) { 327 + for i := 0; i < n; i++ { 328 + p.output = append(p.output, ch) 329 + } 330 + 331 + // update positions 332 + p.pos.Offset += n 333 + if ch == '\n' || ch == '\f' { 334 + p.pos.Line += n 335 + p.pos.Column = 1 336 + 337 + n := p.cfg.Indent + p.indent // include base indentation 338 + for i := 0; i < n; i++ { 339 + p.output = append(p.output, '\t') 340 + } 341 + 342 + // update positions 343 + p.pos.Offset += n 344 + p.pos.Column += n 345 + 346 + return 347 + } 348 + p.pos.Column += n 349 + } 350 + 351 + func mayCombine(prev, next token.Token) (before, after bool) { 352 + s := next.String() 353 + if 'a' <= s[0] && s[0] < 'z' { 354 + return true, true 355 + } 356 + switch prev { 357 + case token.IQUO, token.IREM, token.IDIV, token.IMOD: 358 + return false, false 359 + case token.INT: 360 + before = next == token.PERIOD // 1. 361 + case token.ADD: 362 + before = s[0] == '+' // ++ 363 + case token.SUB: 364 + before = s[0] == '-' // -- 365 + case token.QUO: 366 + before = s[0] == '*' // /* 367 + } 368 + return false, false 369 + }
+19
cue/format/testdata/comments.golden
··· 1 + // Package line 1 group 1 2 + // Package line 2 group 1 3 + 4 + // Package line 1 - group 2 5 + // Package line 2 - group 2 6 + package comments 7 + 8 + // Emit line 1 group 1 9 + 10 + // Emit line 1 group 2 11 + // Emit line 2 group 2 12 + { 13 + // Inside Emit 14 + } 15 + 16 + a: 3 // a line comment 17 + 18 + b: 4 // line comment that is last in the file. 19 + cc: 555 // align comments
+19
cue/format/testdata/comments.input
··· 1 + // Package line 1 group 1 2 + // Package line 2 group 1 3 + 4 + // Package line 1 - group 2 5 + // Package line 2 - group 2 6 + package comments 7 + 8 + // Emit line 1 group 1 9 + 10 + // Emit line 1 group 2 11 + // Emit line 2 group 2 12 + { 13 + // Inside Emit 14 + } 15 + 16 + a: 3 // a line comment 17 + 18 + b: 4 // line comment that is last in the file. 19 + cc: 555 // align comments
+163
cue/format/testdata/expressions.golden
··· 1 + package expressions 2 + 3 + { 4 + a: 1 5 + aaa: 2 6 + 7 + b: 3 8 + 9 + c b a: 4 10 + c bb aaa: 5 11 + c b <Name> a: int 12 + alias = 3.14 13 + 14 + alias2 = foo 15 + aaalias = foo 16 + b: bar 17 + 18 + bottom1: _|_ 19 + bottom2: _|_ // converted to compact symbol 20 + 21 + empty: {} 22 + emptyNewLine: { 23 + 24 + } 25 + someObject: { 26 + a: 8 27 + aa: 9 28 + aaa: 10 29 + } 30 + 31 + e: 1 + 2*3 32 + e: 1 * 2 * 3 // error 33 + e: 2..3 34 + e: 2..(3 + 4) 35 + ex: 2..3 + 4*5 36 + e: (2..3)..4 37 + e: 1 + 2 + 3 // error 38 + 39 + e: s[1+2] 40 + e: s[1:2] 41 + e: s[1+2 : 2+4] 42 + e: s[2] 43 + e: s[2*3] 44 + e: s[1+2*3] 45 + 46 + e: f(3 + 4 + 5) 47 + e: f(3 * 4 * 5) 48 + e: f(3 + 4*5) 49 + 50 + e: f(3 + 4 div 5) 51 + 52 + e: 3 < 4 && 5 > 4 53 + e: a || b && c || d 54 + 55 + e: a + +b*3 56 + e: -a - -b 57 + 58 + e: b + c 59 + e: b*c + d 60 + e: a*b + c 61 + e: a - b - c 62 + e: a - (b - c) 63 + e: a - b*c 64 + e: a - (b * c) 65 + e: a * b / c 66 + e: a div b + 5 67 + e: a / b 68 + e: x[a | b] 69 + e: x[a/b] 70 + e: a & b 71 + e: a + +b 72 + e: a - -b 73 + e: a div -b 74 + e: x[a*-b] 75 + e: x[a + +b] 76 + e: len(longVariableName) * 2 77 + 78 + e: "\(a)" 79 + e: 'aa \(aaa) aa' 80 + e: "aa \(aaa)" 81 + 82 + e: [1, 2] 83 + e: [1, 2, 3, 4, 84 + 5, 6, 7, 8] 85 + e: [1, 2, 3, 4, 86 + 5, 6, 7, 8, // maybe force additional comma 87 + ] 88 + e: [...] 89 + e: [ 90 + ...] 91 + e: [... 92 + ] 93 + e: [1, 2, ...] 94 + e: [1, 2, 95 + ...] 96 + e: [...int] 97 + e: [...int] 98 + e: [...int | float] 99 + e: [ x for x in someObject if x > 9 ] 100 + e: [ x 101 + for x in someObject 102 + if x > 9 ] 103 + e: [ x 104 + for x in someObject 105 + if x > 9 106 + ] 107 + 108 + [k]: v for k, v in someObject 109 + [k]: v <- 110 + for k, v in someObject 111 + 112 + e: {[k]: v <- 113 + for k, v in someObject 114 + if k > "a" 115 + } 116 + 117 + e: {[k]: v for k, v in someObject if k > "a"} 118 + e: {[k]: v <- 119 + for k, v in someObject if k > "a"} 120 + 121 + e: {[k]: v <- 122 + for k, v in someObject 123 + if k > "a"} 124 + 125 + e: [{ 126 + a: 1, b: 2 127 + }] 128 + 129 + e: [{ 130 + a: 1, b: 2 131 + }, 132 + ] 133 + 134 + e: [{ 135 + a: 1, b: 2 136 + }, { 137 + c: 1, d: 2 138 + }] 139 + 140 + e: [{ 141 + a: 1, b: 2 142 + }, 143 + 3, 144 + 4, 145 + ] 146 + 147 + e: (a, b) -> a + b 148 + e: (a, 149 + b) -> a + b 150 + e: (a, b) -> { 151 + a: b 152 + } 153 + e: (a, 154 + b, 155 + ) -> 156 + { 157 + a: b 158 + } 159 + 160 + e: e.f(1, 2) 161 + 162 + e: (3 + 4) 163 + }
+163
cue/format/testdata/expressions.input
··· 1 + package expressions 2 + 3 + { 4 + a: 1 5 + aaa: 2 6 + 7 + b: 3 8 + 9 + c b a: 4 10 + c bb aaa: 5 11 + c b <Name> a: int 12 + alias = 3.14 13 + 14 + alias2 = foo 15 + aaalias = foo 16 + b: bar 17 + 18 + bottom1: _|_ 19 + bottom2: _|_ // converted to compact symbol 20 + 21 + empty: {} 22 + emptyNewLine: { 23 + 24 + } 25 + someObject: { 26 + a: 8 27 + aa: 9 28 + aaa: 10 29 + } 30 + 31 + e: 1+2*3 32 + e: 1*2*3 // error 33 + e: 2..3 34 + e: 2..(3 + 4) 35 + ex: 2..3+4*5 36 + e: 2..3..4 37 + e: 1 + 2 + 3 // error 38 + 39 + e: s[1+2] 40 + e: s[1:2] 41 + e: s[1+2:2+4] 42 + e: s[2] 43 + e: s[2*3] 44 + e: s[1+2*3] 45 + 46 + e: f(3+4+5) 47 + e: f(3*4*5) 48 + e: f(3+4*5) 49 + 50 + e: f(3 + 4 div 5) 51 + 52 + e: 3<4&&5>4 53 + e: a || b && c || d 54 + 55 + e: a + +b * 3 56 + e: -a - -b 57 + 58 + e: b + c 59 + e: b*c + d 60 + e: a*b + c 61 + e: a - b - c 62 + e: a - (b - c) 63 + e: a - b*c 64 + e: a - (b * c) 65 + e: a * b / c 66 + e: a div b + 5 67 + e: a / b 68 + e: x[a|b] 69 + e: x[a /b] 70 + e: a & b 71 + e: a + +b 72 + e: a - -b 73 + e: a div - b 74 + e: x[a*-b] 75 + e: x[a + +b] 76 + e: len(longVariableName) * 2 77 + 78 + e: "\(a)" 79 + e: 'aa \(aaa) aa' 80 + e: "aa \(aaa)" 81 + 82 + e: [1, 2] 83 + e: [1, 2, 3, 4, 84 + 5, 6, 7, 8] 85 + e: [1, 2, 3, 4, 86 + 5, 6, 7, 8 // maybe force additional comma 87 + ] 88 + e: [...] 89 + e: [ 90 + ...] 91 + e: [... 92 + ] 93 + e: [1, 2, ...] 94 + e: [1, 2, 95 + ...] 96 + e: [...int] 97 + e: [...int,] 98 + e: [...int | float] 99 + e: [ x for x in someObject if x > 9 ] 100 + e: [ x 101 + for x in someObject 102 + if x > 9 ] 103 + e: [ x 104 + for x in someObject 105 + if x > 9 106 + ] 107 + 108 + [k]: v for k, v in someObject 109 + [k]: v <- 110 + for k, v in someObject 111 + 112 + e: { [k]:v <- 113 + for k, v in someObject 114 + if k > "a" 115 + } 116 + 117 + e: { [k]:v for k, v in someObject if k > "a"} 118 + e: { [k]:v <- 119 + for k, v in someObject if k > "a"} 120 + 121 + e: { [k]:v <- 122 + for k, v in someObject 123 + if k > "a"} 124 + 125 + e: [{ 126 + a: 1, b: 2, 127 + }] 128 + 129 + e: [{ 130 + a: 1, b: 2, 131 + }, 132 + ] 133 + 134 + e: [{ 135 + a: 1, b: 2, 136 + }, { 137 + c: 1, d: 2, 138 + }] 139 + 140 + e: [{ 141 + a: 1, b: 2, 142 + }, 143 + 3, 144 + 4, 145 + ] 146 + 147 + e: (a, b) -> a + b 148 + e: (a, 149 + b) -> a + b 150 + e: (a, b) -> { 151 + a: b 152 + } 153 + e: (a, 154 + b 155 + ) -> 156 + { 157 + a: b 158 + } 159 + 160 + e: e.f(1, 2) 161 + 162 + e: ((3 + 4)) 163 + }
+6
cue/format/testdata/simplify.golden
··· 1 + 2 + foo bar: "str" 3 + 4 + a B: 42 5 + 6 + "a.b" "foo-" cc_dd: x
+5
cue/format/testdata/simplify.input
··· 1 + "foo" "bar": "str" 2 + 3 + a "B": 42 4 + 5 + "a.b" "foo-" "cc_dd": x