cue/format: add package

Change-Id: I09091c053009fc29d1426e1037f67933c805d158
diff --git a/cue/format/format.go b/cue/format/format.go
new file mode 100644
index 0000000..e581df5
--- /dev/null
+++ b/cue/format/format.go
@@ -0,0 +1,302 @@
+// Copyright 2018 The CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package format implements standard formatting of CUE configurations.
+package format // import "cuelang.org/go/cue/format"
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+	"strings"
+	"text/tabwriter"
+
+	"cuelang.org/go/cue/ast"
+	"cuelang.org/go/cue/parser"
+	"cuelang.org/go/cue/token"
+)
+
+// An Option sets behavior of the formatter.
+type Option func(c *config)
+
+// FileSet sets the file set that was used for creating a node. The formatter
+// generally formats fine without it.
+func FileSet(fset *token.FileSet) Option {
+	return func(c *config) { c.fset = fset }
+}
+
+// Simplify allows the formatter to simplify output, such as removing
+// unnecessary quotes.
+func Simplify() Option {
+	return func(c *config) { c.simplify = true }
+}
+
+// TODO: other options:
+//
+// const (
+// 	RawFormat Mode = 1 << iota // do not use a tabwriter; if set, UseSpaces is ignored
+// 	TabIndent                  // use tabs for indentation independent of UseSpaces
+// 	UseSpaces                  // use spaces instead of tabs for alignment
+// 	SourcePos                  // emit //line comments to preserve original source positions
+// )
+
+// Node formats node in canonical cue fmt style and writes the result to dst.
+//
+// The node type must be *ast.File, []syntax.Decl, syntax.Expr, syntax.Decl, or
+// syntax.Spec. Node does not modify node. Imports are not sorted for nodes
+// representing partial source files (for instance, if the node is not an
+// *ast.File).
+//
+// The function may return early (before the entire result is written) and
+// return a formatting error, for instance due to an incorrect AST.
+//
+func Node(dst io.Writer, node ast.Node, opt ...Option) error {
+	cfg := newConfig(opt)
+	return cfg.fprint(dst, node)
+}
+
+// Source formats src in canonical cue fmt style and returns the result or an
+// (I/O or syntax) error. src is expected to be a syntactically correct CUE
+// source file, or a list of CUE declarations or statements.
+//
+// If src is a partial source file, the leading and trailing space of src is
+// applied to the result (such that it has the same leading and trailing space
+// as src), and the result is indented by the same amount as the first line of
+// src containing code. Imports are not sorted for partial source files.
+//
+// Caution: Tools relying on consistent formatting based on the installed
+// version of cue (for instance, such as for presubmit checks) should execute
+// that cue binary instead of calling Source.
+//
+func Source(b []byte, opt ...Option) ([]byte, error) {
+	cfg := newConfig(opt)
+	if cfg.fset == nil {
+		cfg.fset = token.NewFileSet()
+	}
+
+	f, err := parser.ParseFile(cfg.fset, "", b,
+		parser.ParseComments, parser.ParseLambdas)
+	if err != nil {
+		return nil, fmt.Errorf("parse: %s", err)
+	}
+
+	// print AST
+	var buf bytes.Buffer
+	if err := cfg.fprint(&buf, f); err != nil {
+		return nil, fmt.Errorf("print: %s", err)
+	}
+	return buf.Bytes(), nil
+}
+
+type config struct {
+	fset *token.FileSet
+
+	UseSpaces bool
+	TabIndent bool
+	Tabwidth  int // default: 8
+	Indent    int // default: 0 (all code is indented at least by this much)
+
+	simplify bool
+}
+
+func newConfig(opt []Option) *config {
+	cfg := &config{Tabwidth: 8, TabIndent: true, UseSpaces: true}
+	for _, o := range opt {
+		o(cfg)
+	}
+	return cfg
+}
+
+// Config defines the output of Fprint.
+func (cfg *config) fprint(output io.Writer, node interface{}) (err error) {
+	// print node
+	var p printer
+	p.init(cfg)
+	if err = printNode(node, &p); err != nil {
+		return err
+	}
+
+	minwidth := cfg.Tabwidth
+
+	padchar := byte('\t')
+	if cfg.UseSpaces {
+		padchar = ' '
+	}
+
+	twmode := tabwriter.DiscardEmptyColumns | tabwriter.StripEscape
+	if cfg.TabIndent {
+		minwidth = 0
+		twmode |= tabwriter.TabIndent
+	}
+
+	tw := tabwriter.NewWriter(output, minwidth, cfg.Tabwidth, 1, padchar, twmode)
+
+	// write printer result via tabwriter/trimmer to output
+	if _, err = tw.Write(p.output); err != nil {
+		return
+	}
+
+	err = tw.Flush()
+	return
+}
+
+// A formatter walks a syntax.Node, interspersed with comments and spacing
+// directives, in the order that they would occur in printed form.
+type formatter struct {
+	*printer
+
+	stack    []frame
+	current  frame
+	nestExpr int
+
+	labelBuf []ast.Label
+}
+
+func newFormatter(p *printer) *formatter {
+	f := &formatter{
+		printer: p,
+		current: frame{
+			settings: settings{
+				nodeSep:   newline,
+				parentSep: newline,
+			},
+		},
+	}
+	return f
+}
+
+type whiteSpace int
+
+const (
+	ignore whiteSpace = 0
+
+	// write a space, or disallow it
+	blank whiteSpace = 1 << iota
+	vtab             // column marker
+	noblank
+
+	nooverride
+
+	comma      // print a comma, unless trailcomma overrides it
+	trailcomma // print a trailing comma unless closed on same line
+	declcomma  // write a comma when not at the end of line
+
+	newline    // write a line in a table
+	formfeed   // next line is not part of the table
+	newsection // add two newlines
+
+	indent   // request indent an extra level after the next newline
+	unindent // unindent a level after the next newline
+	indented // element was indented.
+)
+
+type frame struct {
+	cg  []*ast.CommentGroup
+	pos int8
+
+	settings
+	commentNewline bool
+}
+
+type settings struct {
+	// separator is blank if the current node spans a single line and newline
+	// otherwise.
+	nodeSep   whiteSpace
+	parentSep whiteSpace
+	override  whiteSpace
+}
+
+func (f *formatter) print(a ...interface{}) {
+	for _, x := range a {
+		f.Print(x)
+		switch x.(type) {
+		case string, token.Token: // , *syntax.BasicLit, *syntax.Ident:
+			f.current.pos++
+		}
+	}
+	f.visitComments(f.current.pos)
+}
+
+func (f *formatter) formfeed() whiteSpace {
+	if f.current.nodeSep == blank {
+		return blank
+	}
+	return formfeed
+}
+
+func (f *formatter) wsOverride(def whiteSpace) whiteSpace {
+	if f.current.override == ignore {
+		return def
+	}
+	return f.current.override
+}
+
+func (f *formatter) onOneLine(node ast.Node) bool {
+	a := node.Pos()
+	b := node.End()
+	if f.fset != nil && a.IsValid() && b.IsValid() {
+		return f.lineFor(a) == f.lineFor(b)
+	}
+	// TODO: walk and look at relative positions to determine the same?
+	return false
+}
+
+func (f *formatter) before(node ast.Node) bool {
+	f.stack = append(f.stack, f.current)
+	f.current = frame{settings: f.current.settings}
+	f.current.parentSep = f.current.nodeSep
+
+	if node != nil {
+		if f.current.nodeSep != blank && f.onOneLine(node) {
+			f.current.nodeSep = blank
+		}
+		f.current.cg = node.Comments()
+		f.visitComments(f.current.pos)
+		return true
+	}
+	return false
+}
+
+func (f *formatter) after(node ast.Node) {
+	f.visitComments(127)
+	p := len(f.stack) - 1
+	f.current = f.stack[p]
+	f.stack = f.stack[:p]
+	f.current.pos++
+	f.visitComments(f.current.pos)
+}
+
+func (f *formatter) visitComments(until int8) {
+	c := &f.current
+
+	for ; len(c.cg) > 0 && c.cg[0].Position <= until; c.cg = c.cg[1:] {
+		f.Print(c.cg[0])
+
+		printBlank := false
+		if c.cg[0].Doc {
+			f.Print(newline)
+			printBlank = true
+		}
+		for _, c := range c.cg[0].List {
+			if !printBlank {
+				f.Print(vtab)
+			}
+			f.Print(c.Slash)
+			f.Print(c)
+			if strings.HasPrefix(c.Text, "//") {
+				f.Print(newline)
+			}
+		}
+	}
+}
diff --git a/cue/format/format_test.go b/cue/format/format_test.go
new file mode 100644
index 0000000..c77130f
--- /dev/null
+++ b/cue/format/format_test.go
@@ -0,0 +1,415 @@
+// Copyright 2018 The CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package format
+
+// TODO: port more of the tests of go/printer
+
+import (
+	"bytes"
+	"errors"
+	"flag"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"path/filepath"
+	"testing"
+	"time"
+
+	"cuelang.org/go/cue/ast"
+	"cuelang.org/go/cue/parser"
+	"cuelang.org/go/cue/token"
+)
+
+var (
+	defaultConfig = newConfig([]Option{FileSet(fset)})
+	Fprint        = defaultConfig.fprint
+)
+
+const (
+	dataDir  = "testdata"
+	tabwidth = 8
+)
+
+var update = flag.Bool("update", false, "update golden files")
+
+var fset = token.NewFileSet()
+
+type checkMode uint
+
+const (
+	export checkMode = 1 << iota
+	rawFormat
+	idempotent
+	simplify
+)
+
+// format parses src, prints the corresponding AST, verifies the resulting
+// src is syntactically correct, and returns the resulting src or an error
+// if any.
+func format(src []byte, mode checkMode) ([]byte, error) {
+	// parse src
+	var opts []Option
+	if mode&simplify != 0 {
+		opts = append(opts, Simplify())
+	}
+
+	res, err := Source(src, opts...)
+	if err != nil {
+		return nil, err
+	}
+
+	// make sure formatted output is syntactically correct
+	if _, err := parser.ParseFile(fset, "", res,
+		parser.ParseLambdas, parser.AllErrors); err != nil {
+		return nil, fmt.Errorf("re-parse: %s\n%s", err, res)
+	}
+
+	return res, nil
+}
+
+// lineAt returns the line in text starting at offset offs.
+func lineAt(text []byte, offs int) []byte {
+	i := offs
+	for i < len(text) && text[i] != '\n' {
+		i++
+	}
+	return text[offs:i]
+}
+
+// diff compares a and b.
+func diff(aname, bname string, a, b []byte) error {
+	var buf bytes.Buffer // holding long error message
+
+	// compare lengths
+	if len(a) != len(b) {
+		fmt.Fprintf(&buf, "\nlength changed: len(%s) = %d, len(%s) = %d", aname, len(a), bname, len(b))
+	}
+
+	// compare contents
+	line := 1
+	offs := 1
+	for i := 0; i < len(a) && i < len(b); i++ {
+		ch := a[i]
+		if ch != b[i] {
+			fmt.Fprintf(&buf, "\n%s:%d:%d: %s", aname, line, i-offs+1, lineAt(a, offs))
+			fmt.Fprintf(&buf, "\n%s:%d:%d: %s", bname, line, i-offs+1, lineAt(b, offs))
+			fmt.Fprintf(&buf, "\n\n")
+			break
+		}
+		if ch == '\n' {
+			line++
+			offs = i + 1
+		}
+	}
+
+	if buf.Len() > 0 {
+		return errors.New(buf.String())
+	}
+	return nil
+}
+
+func runcheck(t *testing.T, source, golden string, mode checkMode) {
+	src, err := ioutil.ReadFile(source)
+	if err != nil {
+		t.Error(err)
+		return
+	}
+
+	res, err := format(src, mode)
+	if err != nil {
+		t.Error(err)
+		return
+	}
+
+	// update golden files if necessary
+	if *update {
+		if err := ioutil.WriteFile(golden, res, 0644); err != nil {
+			t.Error(err)
+		}
+		return
+	}
+
+	// get golden
+	gld, err := ioutil.ReadFile(golden)
+	if err != nil {
+		t.Error(err)
+		return
+	}
+
+	// formatted source and golden must be the same
+	if err := diff(source, golden, res, gld); err != nil {
+		t.Error(err)
+		return
+	}
+
+	if mode&idempotent != 0 {
+		// formatting golden must be idempotent
+		// (This is very difficult to achieve in general and for now
+		// it is only checked for files explicitly marked as such.)
+		res, err = format(gld, mode)
+		if err := diff(golden, fmt.Sprintf("format(%s)", golden), gld, res); err != nil {
+			t.Errorf("golden is not idempotent: %s", err)
+		}
+	}
+}
+
+func check(t *testing.T, source, golden string, mode checkMode) {
+	// run the test
+	cc := make(chan int)
+	go func() {
+		runcheck(t, source, golden, mode)
+		cc <- 0
+	}()
+
+	// wait with timeout
+	select {
+	case <-time.After(100000 * time.Second): // plenty of a safety margin, even for very slow machines
+		// test running past time out
+		t.Errorf("%s: running too slowly", source)
+	case <-cc:
+		// test finished within allotted time margin
+	}
+}
+
+type entry struct {
+	source, golden string
+	mode           checkMode
+}
+
+// Use go test -update to create/update the respective golden files.
+var data = []entry{
+	{"comments.input", "comments.golden", 0},
+	{"simplify.input", "simplify.golden", simplify},
+	{"expressions.input", "expressions.golden", 0},
+}
+
+func TestFiles(t *testing.T) {
+	t.Parallel()
+	for _, e := range data {
+		source := filepath.Join(dataDir, e.source)
+		golden := filepath.Join(dataDir, e.golden)
+		mode := e.mode
+		t.Run(e.source, func(t *testing.T) {
+			t.Parallel()
+			check(t, source, golden, mode)
+			// TODO(gri) check that golden is idempotent
+			//check(t, golden, golden, e.mode)
+		})
+	}
+}
+
+// Verify that the printer can be invoked during initialization.
+func init() {
+	const name = "foobar"
+	var buf bytes.Buffer
+	if err := Fprint(&buf, &ast.Ident{Name: name}); err != nil {
+		panic(err) // error in test
+	}
+	// in debug mode, the result contains additional information;
+	// ignore it
+	if s := buf.String(); !debug && s != name {
+		panic("got " + s + ", want " + name)
+	}
+}
+
+// Verify that the printer doesn't crash if the AST contains BadXXX nodes.
+func TestBadNodes(t *testing.T) {
+	const src = "package p\n("
+	const res = "package p\n\n(BadExpr)\n"
+	f, err := parser.ParseFile(fset, "", src, parser.ParseComments)
+	if err == nil {
+		t.Error("expected illegal program") // error in test
+	}
+	var buf bytes.Buffer
+	Fprint(&buf, f)
+	if buf.String() != res {
+		t.Errorf("got %q, expected %q", buf.String(), res)
+	}
+}
+
+// idents is an iterator that returns all idents in f via the result channel.
+func idents(f *ast.File) <-chan *ast.Ident {
+	v := make(chan *ast.Ident)
+	go func() {
+		ast.Walk(f, func(n ast.Node) bool {
+			if ident, ok := n.(*ast.Ident); ok {
+				v <- ident
+			}
+			return true
+		}, nil)
+		close(v)
+	}()
+	return v
+}
+
+// identCount returns the number of identifiers found in f.
+func identCount(f *ast.File) int {
+	n := 0
+	for range idents(f) {
+		n++
+	}
+	return n
+}
+
+// Verify that the SourcePos mode emits correct //line comments
+// by testing that position information for matching identifiers
+// is maintained.
+func TestSourcePos(t *testing.T) {
+	const src = `package p
+
+import (
+	"go/printer"
+	"math"
+	"regexp"
+)
+
+pi = 3.14  // TODO: allow on same line
+xx = 0
+t: {
+	x: int
+	y: int
+	z: int
+	u: number
+	v: number
+	w: number
+}
+e: a*t.x + b*t.y
+
+// two extra lines here // ...
+e2: c*t.z
+`
+
+	// parse original
+	f1, err := parser.ParseFile(fset, "src", src, parser.ParseComments)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// pretty-print original
+	var buf bytes.Buffer
+	err = (&config{UseSpaces: true, Tabwidth: 8}).fprint(&buf, f1)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// parse pretty printed original
+	// (//line comments must be interpreted even w/o syntax.ParseComments set)
+	f2, err := parser.ParseFile(fset, "", buf.Bytes(),
+		parser.AllErrors, parser.ParseLambdas, parser.ParseComments)
+	if err != nil {
+		t.Fatalf("%s\n%s", err, buf.Bytes())
+	}
+
+	// At this point the position information of identifiers in f2 should
+	// match the position information of corresponding identifiers in f1.
+
+	// number of identifiers must be > 0 (test should run) and must match
+	n1 := identCount(f1)
+	n2 := identCount(f2)
+	if n1 == 0 {
+		t.Fatal("got no idents")
+	}
+	if n2 != n1 {
+		t.Errorf("got %d idents; want %d", n2, n1)
+	}
+
+	// verify that all identifiers have correct line information
+	i2range := idents(f2)
+	for i1 := range idents(f1) {
+		i2 := <-i2range
+
+		if i2 == nil || i1 == nil {
+			t.Fatal("non nil identifiers")
+		}
+		if i2.Name != i1.Name {
+			t.Errorf("got ident %s; want %s", i2.Name, i1.Name)
+		}
+
+		l1 := fset.Position(i1.Pos()).Line
+		l2 := fset.Position(i2.Pos()).Line
+		if l2 != l1 {
+			t.Errorf("got line %d; want %d for %s", l2, l1, i1.Name)
+		}
+	}
+
+	if t.Failed() {
+		t.Logf("\n%s", buf.Bytes())
+	}
+}
+
+var decls = []string{
+	`import "fmt"`,
+	"pi = 3.1415\ne = 2.71828\n\nx = pi",
+}
+
+func TestDeclLists(t *testing.T) {
+	for _, src := range decls {
+		file, err := parser.ParseFile(fset, "", "package p\n"+src, parser.ParseComments)
+		if err != nil {
+			panic(err) // error in test
+		}
+
+		var buf bytes.Buffer
+		err = Fprint(&buf, file.Decls) // only print declarations
+		if err != nil {
+			panic(err) // error in test
+		}
+
+		out := buf.String()
+
+		if out != src {
+			t.Errorf("\ngot : %q\nwant: %q\n", out, src)
+		}
+	}
+}
+
+var stmts = []string{
+	"i := 0",
+	"select {}\nvar a, b = 1, 2\nreturn a + b",
+	"go f()\ndefer func() {}()",
+}
+
+type limitWriter struct {
+	remaining int
+	errCount  int
+}
+
+func (l *limitWriter) Write(buf []byte) (n int, err error) {
+	n = len(buf)
+	if n >= l.remaining {
+		n = l.remaining
+		err = io.EOF
+		l.errCount++
+	}
+	l.remaining -= n
+	return n, err
+}
+
+// TextX is a skeleton test that can be filled in for debugging one-off cases.
+// Do not remove.
+func TestX(t *testing.T) {
+	const src = `
+	{ e: k <-
+	for a, v in s}
+	a: b
+
+`
+	b, err := format([]byte(src), 0)
+	if err != nil {
+		t.Error(err)
+	}
+	_ = b
+	// t.Error("\n", string(b))
+}
diff --git a/cue/format/node.go b/cue/format/node.go
new file mode 100644
index 0000000..cb138c7
--- /dev/null
+++ b/cue/format/node.go
@@ -0,0 +1,688 @@
+// Copyright 2018 The CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package format
+
+import (
+	"fmt"
+	"strconv"
+	"strings"
+
+	"cuelang.org/go/cue/ast"
+	"cuelang.org/go/cue/parser"
+	"cuelang.org/go/cue/token"
+)
+
+func printNode(node interface{}, f *printer) error {
+	s := newFormatter(f)
+
+	// format node
+	f.allowed = nooverride // gobble initial whitespace.
+	switch x := node.(type) {
+	case *ast.File:
+		s.file(x)
+	case ast.Expr:
+		s.expr(x)
+	case ast.Decl:
+		s.decl(x)
+	// case ast.Node: // TODO: do we need this?
+	// 	s.walk(x)
+	case []ast.Decl:
+		s.walkDeclList(x)
+	default:
+		goto unsupported
+	}
+
+	return nil
+
+unsupported:
+	return fmt.Errorf("cue/format: unsupported node type %T", node)
+}
+
+// Helper functions for common node lists. They may be empty.
+
+func (f *formatter) walkDeclList(list []ast.Decl) {
+	f.before(nil)
+	for i, x := range list {
+		if i > 0 {
+			f.print(declcomma)
+		}
+		f.decl(x)
+		f.print(f.current.parentSep)
+	}
+	f.after(nil)
+}
+
+func (f *formatter) walkSpecList(list []*ast.ImportSpec) {
+	f.before(nil)
+	for _, x := range list {
+		f.importSpec(x)
+	}
+	f.after(nil)
+}
+
+func (f *formatter) walkClauseList(list []ast.Clause) {
+	f.before(nil)
+	for _, x := range list {
+		f.clause(x)
+	}
+	f.after(nil)
+}
+
+func (f *formatter) walkExprList(list []ast.Expr, depth int) {
+	f.before(nil)
+	for _, x := range list {
+		f.before(x)
+		f.exprRaw(x, token.LowestPrec, depth)
+		f.print(comma, blank)
+		f.after(x)
+	}
+	f.after(nil)
+}
+
+func (f *formatter) file(file *ast.File) {
+	f.before(file)
+	if file.Name != nil {
+		f.print(file.Package, "package")
+		f.print(blank, file.Name, newline, newsection)
+	}
+	f.current.pos = 3
+	f.visitComments(3)
+	f.walkDeclList(file.Decls)
+	f.after(file)
+	f.print(token.EOF)
+}
+func (f *formatter) decl(decl ast.Decl) {
+	if decl == nil {
+		return
+	}
+	if !f.before(decl) {
+		goto after
+	}
+	switch n := decl.(type) {
+	case *ast.Field:
+		// shortcut single-element structs.
+		lastSize := len(f.labelBuf)
+		f.labelBuf = f.labelBuf[:0]
+		first := n.Label
+		for {
+			obj, ok := n.Value.(*ast.StructLit)
+			if !ok || len(obj.Elts) != 1 || (obj.Lbrace.IsValid() && !f.printer.cfg.simplify) {
+				break
+			}
+
+			// Verify that struct doesn't have inside comments and that
+			// element doesn't have doc comments.
+			hasComments := len(obj.Elts[0].Comments()) > 0
+			for _, c := range obj.Comments() {
+				if c.Position == 1 || c.Position == 2 {
+					hasComments = true
+				}
+			}
+			if hasComments {
+				break
+			}
+
+			mem, ok := obj.Elts[0].(*ast.Field)
+			if !ok {
+				break
+			}
+			f.labelBuf = append(f.labelBuf, mem.Label)
+			n = mem
+		}
+
+		if lastSize != len(f.labelBuf) {
+			f.print(formfeed)
+		}
+
+		f.before(nil)
+		f.label(first)
+		for _, x := range f.labelBuf {
+			f.print(blank, nooverride)
+			f.label(x)
+		}
+		f.after(nil)
+
+		nextFF := f.nextNeedsFormfeed(n.Value)
+		tab := vtab
+		if nextFF || f.nestExpr >= 1 {
+			tab = blank
+		}
+
+		f.print(n.Colon, token.COLON, tab)
+		if n.Value != nil {
+			f.expr(n.Value)
+		} else {
+			f.current.pos++
+			f.visitComments(f.current.pos)
+		}
+
+		if nextFF {
+			f.print(formfeed)
+		}
+
+	case *ast.ComprehensionDecl:
+		f.decl(n.Field)
+		f.print(blank)
+		if n.Select != token.NoPos {
+			f.print(n.Select, token.ARROW, blank)
+		}
+		f.print(indent)
+		f.walkClauseList(n.Clauses)
+		f.print(unindent)
+		f.print("") // force whitespace to be written
+
+	case *ast.BadDecl:
+		f.print(n.From, "*bad decl*", declcomma)
+
+	case *ast.ImportDecl:
+		f.print(n.Import, "import")
+		if len(n.Specs) == 0 {
+			f.print(blank, n.Lparen, token.LPAREN, n.Rparen, token.RPAREN, newline)
+		}
+		switch {
+		case len(n.Specs) == 1:
+			if !n.Lparen.IsValid() {
+				f.print(blank)
+				f.walkSpecList(n.Specs)
+				break
+			}
+			fallthrough
+		default:
+			f.print(blank, n.Lparen, token.LPAREN, newline, indent)
+			f.walkSpecList(n.Specs)
+			f.print(unindent, newline, n.Rparen, token.RPAREN, newline)
+		}
+		f.print(newsection)
+
+	case *ast.EmitDecl:
+		f.expr(n.Expr)
+		f.print(newline, newsection) // force newline
+
+	case *ast.Alias:
+		f.expr(n.Ident)
+		f.print(blank, n.Equal, token.BIND, blank)
+		f.expr(n.Expr)
+		f.print(declcomma, newline) // implied
+	}
+after:
+	f.after(decl)
+}
+
+func (f *formatter) nextNeedsFormfeed(n ast.Expr) bool {
+	switch x := n.(type) {
+	case *ast.StructLit:
+		return true
+	case *ast.BasicLit:
+		return strings.IndexByte(x.Value, '\n') >= 0
+	case *ast.ListLit:
+		return true
+	}
+	return false
+}
+
+func (f *formatter) importSpec(x *ast.ImportSpec) {
+	if x.Name != nil {
+		f.label(x.Name)
+		f.print(blank)
+	} else {
+		f.current.pos++
+		f.visitComments(f.current.pos)
+	}
+	f.expr(x.Path)
+	f.print(newline)
+}
+
+func (f *formatter) label(l ast.Label) {
+	switch n := l.(type) {
+	case *ast.Ident:
+		f.print(n.NamePos, n)
+
+	case *ast.BasicLit:
+		if f.cfg.simplify && n.Kind == token.STRING && len(n.Value) > 2 {
+			s := n.Value
+			unquoted, err := strconv.Unquote(s)
+			fset := token.NewFileSet()
+			if err == nil {
+				e, _ := parser.ParseExpr(fset, "check", unquoted)
+				if _, ok := e.(*ast.Ident); ok {
+					f.print(n.ValuePos, unquoted)
+					break
+				}
+			}
+		}
+		f.print(n.ValuePos, n.Value)
+
+	case *ast.TemplateLabel:
+		f.print(n.Langle, token.LSS, indent)
+		f.label(n.Ident)
+		f.print(unindent, n.Rangle, token.GTR)
+
+	case *ast.Interpolation:
+		f.expr(n)
+
+	case *ast.ExprLabel:
+		f.print(n.Lbrack, token.LBRACK, indent)
+		f.expr(n.Label)
+		f.print(unindent, n.Rbrack, token.RBRACK)
+
+	default:
+		panic(fmt.Sprintf("unknown label type %T", n))
+	}
+}
+
+func (f *formatter) expr(x ast.Expr) {
+	const depth = 1
+	f.expr1(x, token.LowestPrec, depth)
+}
+
+func (f *formatter) expr0(x ast.Expr, depth int) {
+	f.expr1(x, token.LowestPrec, depth)
+}
+
+func (f *formatter) expr1(expr ast.Expr, prec1, depth int) {
+	if f.before(expr) {
+		f.exprRaw(expr, prec1, depth)
+	}
+	f.after(expr)
+}
+
+func (f *formatter) exprRaw(expr ast.Expr, prec1, depth int) {
+
+	switch x := expr.(type) {
+	case *ast.BadExpr:
+		f.print(x.From, "BadExpr")
+
+	case *ast.BottomLit:
+		f.print(x.Bottom, token.BOTTOM)
+
+	case *ast.Ident:
+		f.print(x.NamePos, x)
+
+	case *ast.BinaryExpr:
+		if depth < 1 {
+			f.internalError("depth < 1:", depth)
+			depth = 1
+		}
+		if prec1 == 8 { // ..
+			prec1 = 9 // always parentheses for values of ranges
+		}
+		f.binaryExpr(x, prec1, cutoff(x, depth), depth)
+
+	case *ast.UnaryExpr:
+		const prec = token.UnaryPrec
+		if prec < prec1 {
+			// parenthesis needed
+			f.print(token.LPAREN, nooverride)
+			f.expr(x)
+			f.print(token.RPAREN)
+		} else {
+			// no parenthesis needed
+			f.print(x.OpPos, x.Op, nooverride)
+			f.expr1(x.X, prec, depth)
+		}
+
+	case *ast.BasicLit:
+		f.print(x.ValuePos, x)
+
+	case *ast.Interpolation:
+		f.before(nil)
+		for _, x := range x.Elts {
+			f.expr0(x, depth+1)
+		}
+		f.after(nil)
+
+	case *ast.ParenExpr:
+		if _, hasParens := x.X.(*ast.ParenExpr); hasParens {
+			// don't print parentheses around an already parenthesized expression
+			// TODO: consider making this more general and incorporate precedence levels
+			f.expr0(x.X, depth)
+		} else {
+			f.print(x.Lparen, token.LPAREN)
+			f.expr0(x.X, reduceDepth(depth)) // parentheses undo one level of depth
+			f.print(x.Rparen, token.RPAREN)
+		}
+
+	case *ast.SelectorExpr:
+		f.selectorExpr(x, depth)
+
+	case *ast.IndexExpr:
+		f.expr1(x.X, token.HighestPrec, 1)
+		f.print(x.Lbrack, token.LBRACK)
+		f.expr0(x.Index, depth+1)
+		f.print(x.Rbrack, token.RBRACK)
+
+	case *ast.SliceExpr:
+		f.expr1(x.X, token.HighestPrec, 1)
+		f.print(x.Lbrack, token.LBRACK)
+		indices := []ast.Expr{x.Low, x.High}
+		for i, y := range indices {
+			if i > 0 {
+				// blanks around ":" if both sides exist and either side is a binary expression
+				x := indices[i-1]
+				if depth <= 1 && x != nil && y != nil && (isBinary(x) || isBinary(y)) {
+					f.print(blank, token.COLON, blank)
+				} else {
+					f.print(token.COLON)
+				}
+			}
+			if y != nil {
+				f.expr0(y, depth+1)
+			}
+		}
+		f.print(x.Rbrack, token.RBRACK)
+
+	case *ast.CallExpr:
+		if len(x.Args) > 1 {
+			depth++
+		}
+		wasIndented := f.possibleSelectorExpr(x.Fun, token.HighestPrec, depth)
+		f.print(x.Lparen, token.LPAREN)
+		f.walkExprList(x.Args, depth)
+		f.print(trailcomma, noblank, x.Rparen, token.RPAREN)
+		if wasIndented {
+			f.print(unindent)
+		}
+
+	case *ast.Ellipsis:
+		f.print(x.Ellipsis, token.ELLIPSIS)
+		if x.Elt != nil {
+			f.expr(x.Elt) // TODO
+		}
+
+	case *ast.StructLit:
+		f.print(x.Lbrace, token.LBRACE, noblank, f.formfeed(), indent)
+		f.walkDeclList(x.Elts)
+		f.matchUnindent()
+		f.print(noblank, x.Rbrace, token.RBRACE)
+
+	case *ast.ListLit:
+		f.print(x.Lbrack, token.LBRACK, indent)
+		f.walkExprList(x.Elts, 1)
+		if x.Ellipsis != token.NoPos || x.Type != nil {
+			f.print(x.Ellipsis, token.ELLIPSIS)
+			if x.Type != nil && !isTop(x.Type) {
+				f.expr(x.Type)
+			}
+		} else {
+			f.print(trailcomma, noblank)
+			f.current.pos += 2
+			f.visitComments(f.current.pos)
+		}
+		f.matchUnindent()
+		f.print(noblank, x.Rbrack, token.RBRACK)
+
+	case *ast.ListComprehension:
+		f.print(x.Lbrack, token.LBRACK, blank, indent)
+		f.expr(x.Expr)
+		f.print(blank)
+		f.walkClauseList(x.Clauses)
+		f.print(unindent, f.wsOverride(blank), x.Rbrack, token.RBRACK)
+
+	case *ast.LambdaExpr:
+		f.print(x.Lparen, token.LPAREN, indent, noblank)
+
+		f.before(nil)
+		for _, x := range x.Params {
+			f.label(x.Label)
+			if x.Colon.IsValid() {
+				f.print(x.Colon, token.COLON, blank)
+				f.expr(x.Value)
+			}
+			f.print(comma, blank)
+		}
+		f.print(trailcomma, noblank)
+		f.after(nil)
+
+		f.print(trailcomma, noblank, unindent)
+		f.print(x.Rparen, token.RPAREN, blank)
+		f.print(token.LAMBDA, blank)
+		f.expr(x.Expr)
+
+	default:
+		panic(fmt.Sprintf("unimplemented type %T", x))
+	}
+	return
+}
+
+func (f *formatter) clause(clause ast.Clause) {
+	switch n := clause.(type) {
+	case *ast.ForClause:
+		f.print(blank, n.For, "for", blank)
+		if n.Key != nil {
+			f.label(n.Key)
+			f.print(n.Colon, token.COMMA, blank)
+		} else {
+			f.current.pos++
+			f.visitComments(f.current.pos)
+		}
+		f.label(n.Value)
+		f.print(blank, n.In, "in", blank)
+		f.expr(n.Source)
+
+	case *ast.IfClause:
+		f.print(blank, n.If, "if", blank)
+		f.expr(n.Condition)
+
+	default:
+		panic("unknown clause type")
+	}
+}
+
+func walkBinary(e *ast.BinaryExpr) (has6, has7, has8 bool, maxProblem int) {
+	switch e.Op.Precedence() {
+	case 6:
+		has6 = true
+	case 7:
+		has7 = true
+	case 8:
+		has8 = true
+	}
+
+	switch l := e.X.(type) {
+	case *ast.BinaryExpr:
+		if l.Op.Precedence() < e.Op.Precedence() {
+			// parens will be inserted.
+			// pretend this is an *syntax.ParenExpr and do nothing.
+			break
+		}
+		h6, h7, h8, mp := walkBinary(l)
+		has6 = has6 || h6
+		has7 = has7 || h7
+		has8 = has8 || h8
+		if maxProblem < mp {
+			maxProblem = mp
+		}
+	}
+
+	switch r := e.Y.(type) {
+	case *ast.BinaryExpr:
+		if r.Op.Precedence() <= e.Op.Precedence() {
+			// parens will be inserted.
+			// pretend this is an *syntax.ParenExpr and do nothing.
+			break
+		}
+		h6, h7, h8, mp := walkBinary(r)
+		has6 = has6 || h6
+		has7 = has7 || h7
+		has8 = has8 || h8
+		if maxProblem < mp {
+			maxProblem = mp
+		}
+
+	case *ast.UnaryExpr:
+		switch e.Op.String() + r.Op.String() {
+		case "/*":
+			maxProblem = 8
+		case "++", "--":
+			if maxProblem < 6 {
+				maxProblem = 6
+			}
+		}
+	}
+	return
+}
+
+func cutoff(e *ast.BinaryExpr, depth int) int {
+	has6, has7, has8, maxProblem := walkBinary(e)
+	if maxProblem > 0 {
+		return maxProblem + 1
+	}
+	if (has6 || has7) && has8 {
+		if depth == 1 {
+			return 8
+		}
+		if has7 {
+			return 7
+		}
+		return 6
+	}
+	if has6 && has7 {
+		if depth == 1 {
+			return 7
+		}
+		return 6
+	}
+	if depth == 1 {
+		return 8
+	}
+	return 6
+}
+
+func diffPrec(expr ast.Expr, prec int) int {
+	x, ok := expr.(*ast.BinaryExpr)
+	if !ok || prec != x.Op.Precedence() {
+		return 1
+	}
+	return 0
+}
+
+func reduceDepth(depth int) int {
+	depth--
+	if depth < 1 {
+		depth = 1
+	}
+	return depth
+}
+
+// Format the binary expression: decide the cutoff and then format.
+// Let's call depth == 1 Normal mode, and depth > 1 Compact mode.
+// (Algorithm suggestion by Russ Cox.)
+//
+// The precedences are:
+//  8             ..
+//	7             *  /  % quo rem div mod
+//	6             +  -
+//	5             ==  !=  <  <=  >  >=
+//	4             &&
+//	3             ||
+//	2             &
+//	1             |
+//
+// The only decision is whether there will be spaces around levels 6 and 7.
+// There are never spaces at level 8 (unary), and always spaces at levels 5 and below.
+//
+// To choose the cutoff, look at the whole expression but excluding primary
+// expressions (function calls, parenthesized exprs), and apply these rules:
+//
+//	1) If there is a binary operator with a right side unary operand
+//	   that would clash without a space, the cutoff must be (in order):
+//
+//		/*	8
+//		++	7 // not necessary, but to avoid confusion
+//		--	7
+//
+//         (Comparison operators always have spaces around them.)
+//
+//	2) If there is a mix of level 7 and level 6 operators, then the cutoff
+//	   is 7 (use spaces to distinguish precedence) in Normal mode
+//	   and 6 (never use spaces) in Compact mode.
+//
+//	3) If there are no level 6 operators or no level 7 operators, then the
+//	   cutoff is 8 (always use spaces) in Normal mode
+//	   and 6 (never use spaces) in Compact mode.
+//
+func (f *formatter) binaryExpr(x *ast.BinaryExpr, prec1, cutoff, depth int) {
+	f.nestExpr++
+	defer func() { f.nestExpr-- }()
+
+	prec := x.Op.Precedence()
+	if prec < prec1 {
+		// parenthesis needed
+		// Note: The parser inserts an syntax.ParenExpr node; thus this case
+		//       can only occur if the AST is created in a different way.
+		// defer p.pushComment(nil).pop()
+		f.print(token.LPAREN, nooverride)
+		f.expr0(x, reduceDepth(depth)) // parentheses undo one level of depth
+		f.print(token.RPAREN)
+		return
+	}
+
+	printBlank := prec < cutoff
+
+	ws := indent
+	f.expr1(x.X, prec, depth+diffPrec(x.X, prec))
+	f.print(nooverride)
+	if printBlank {
+		f.print(blank)
+	}
+	f.print(x.OpPos, x.Op)
+	if x.Y.Pos().IsNewline() {
+		// at least one line break, but respect an extra empty line
+		// in the source
+		f.print(formfeed)
+		printBlank = false // no blank after line break
+	} else {
+		f.print(nooverride)
+	}
+	if printBlank {
+		f.print(blank)
+	}
+	f.expr1(x.Y, prec+1, depth+1)
+	if ws == ignore {
+		f.print(unindent)
+	}
+}
+
+func isBinary(expr ast.Expr) bool {
+	_, ok := expr.(*ast.BinaryExpr)
+	return ok
+}
+
+func (f *formatter) possibleSelectorExpr(expr ast.Expr, prec1, depth int) bool {
+	if x, ok := expr.(*ast.SelectorExpr); ok {
+		return f.selectorExpr(x, depth)
+	}
+	f.expr1(expr, prec1, depth)
+	return false
+}
+
+// selectorExpr handles an *syntax.SelectorExpr node and returns whether x spans
+// multiple lines.
+func (f *formatter) selectorExpr(x *ast.SelectorExpr, depth int) bool {
+	f.expr1(x.X, token.HighestPrec, depth)
+	f.print(token.PERIOD)
+	if x.Sel.Pos().IsNewline() {
+		f.print(indent, formfeed, x.Sel.Pos(), x.Sel)
+		f.print(unindent)
+		return true
+	}
+	f.print(x.Sel.Pos(), x.Sel)
+	return false
+}
+
+func isTop(e ast.Expr) bool {
+	ident, ok := e.(*ast.Ident)
+	return ok && ident.Name == "_"
+}
diff --git a/cue/format/printer.go b/cue/format/printer.go
new file mode 100644
index 0000000..10a2b7b
--- /dev/null
+++ b/cue/format/printer.go
@@ -0,0 +1,369 @@
+// Copyright 2018 The CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package format
+
+import (
+	"fmt"
+	"os"
+	"text/tabwriter"
+
+	"cuelang.org/go/cue/ast"
+	"cuelang.org/go/cue/token"
+)
+
+// A printer takes the stream of formatting tokens and spacing directives
+// produced by the formatter and adjusts the spacing based on the original
+// source code.
+type printer struct {
+	w tabwriter.Writer
+
+	fset *token.FileSet
+	cfg  *config
+
+	allowed     whiteSpace
+	requested   whiteSpace
+	indentStack []whiteSpace
+	indentPos   int
+
+	pos token.Position // current pos in AST
+
+	lastTok token.Token // last token printed (syntax.ILLEGAL if it's whitespace)
+
+	output      []byte
+	indent      int
+	spaceBefore bool
+}
+
+func (p *printer) init(cfg *config) {
+	p.cfg = cfg
+	p.pos = token.Position{Line: 1, Column: 1}
+}
+
+const debug = false
+
+func (p *printer) internalError(msg ...interface{}) {
+	if debug {
+		fmt.Print(p.pos.String() + ": ")
+		fmt.Println(msg...)
+		panic("go/printer")
+	}
+}
+
+func (p *printer) lineFor(pos token.Pos) int {
+	if p.fset == nil {
+		return 0
+	}
+	return p.fset.Position(pos).Line
+}
+
+func (p *printer) Print(v interface{}) {
+	var (
+		impliedComma = false
+		isLit        bool
+		data         string
+		nextWS       whiteSpace
+	)
+	switch x := v.(type) {
+	case token.Token:
+		s := x.String()
+		before, after := mayCombine(p.lastTok, x)
+		if before && !p.spaceBefore {
+			// the previous and the current token must be
+			// separated by a blank otherwise they combine
+			// into a different incorrect token sequence
+			// (except for syntax.INT followed by a '.' this
+			// should never happen because it is taken care
+			// of via binary expression formatting)
+			if p.allowed&blank != 0 {
+				p.internalError("whitespace buffer not empty")
+			}
+			p.allowed |= blank
+		}
+		if after {
+			nextWS = blank
+		}
+		data = s
+		switch x {
+		case token.EOF:
+			data = ""
+			p.allowed = newline
+			p.allowed &^= newsection
+		case token.LPAREN, token.LBRACK, token.LBRACE:
+		case token.RPAREN, token.RBRACK, token.RBRACE:
+			impliedComma = true
+		}
+		p.lastTok = x
+
+	case *ast.BasicLit:
+		data = x.Value
+		isLit = true
+		impliedComma = true
+		p.lastTok = x.Kind
+
+	case *ast.Ident:
+		data = x.Name
+		impliedComma = true
+		p.lastTok = token.IDENT
+
+	case string:
+		data = x
+		impliedComma = true
+		p.lastTok = token.STRING
+
+	case *ast.CommentGroup:
+		rel := x.Pos().RelPos()
+		if x.Line { // TODO: we probably don't need this.
+			rel = token.Blank
+		}
+		switch rel {
+		case token.NoRelPos:
+		case token.Newline, token.NewSection:
+		case token.Blank, token.Elided:
+			p.allowed |= blank
+			fallthrough
+		case token.NoSpace:
+			p.allowed &^= newline | newsection | formfeed | declcomma
+		}
+		return
+
+	case *ast.Comment:
+		// TODO: if implied comma, postpone comment
+		data = x.Text
+		p.lastTok = token.COMMENT
+		break
+
+	case whiteSpace:
+		p.allowed |= x
+		return
+
+	case token.Pos:
+		// TODO: should we use a known file position to synchronize? Go does,
+		// but we don't really have to.
+		// pos := p.fset.Position(x)
+		if x.HasRelPos() {
+			if p.allowed&nooverride == 0 {
+				requested := p.allowed
+				switch x.RelPos() {
+				case token.NoSpace:
+					requested &^= newline | newsection | formfeed
+				case token.Blank:
+					requested |= blank
+					requested &^= newline | newsection | formfeed
+				case token.Newline:
+					requested |= newline
+				case token.NewSection:
+					requested |= newsection
+				}
+				p.writeWhitespace(requested)
+				p.allowed = 0
+				p.requested = 0
+			}
+			// p.pos = pos
+		}
+		return
+
+	default:
+		fmt.Fprintf(os.Stderr, "print: unsupported argument %v (%T)\n", x, x)
+		panic("go/printer type")
+	}
+
+	p.writeWhitespace(p.allowed)
+	p.allowed = 0
+	p.requested = 0
+	p.writeString(data, isLit)
+	p.allowed = nextWS
+	_ = impliedComma // TODO: delay comment printings
+}
+
+func (p *printer) writeWhitespace(ws whiteSpace) {
+	if debug {
+		p.output = append(p.output, fmt.Sprintf("/*=%x=*/", p.allowed)...) // do not update f.pos!
+	}
+
+	if ws&comma != 0 {
+		switch {
+		case ws&(newsection|newline|formfeed) != 0,
+			ws&trailcomma == 0:
+			p.writeByte(',', 1)
+		}
+	}
+	if ws&indent != 0 {
+		p.markLineIndent(ws)
+	}
+	if ws&unindent != 0 {
+		p.markUnindentLine()
+	}
+	switch {
+	case ws&newsection != 0:
+		p.maybeIndentLine(ws)
+		p.writeByte('\f', 2)
+		p.spaceBefore = true
+	case ws&formfeed != 0:
+		p.maybeIndentLine(ws)
+		p.writeByte('\f', 1)
+		p.spaceBefore = true
+	case ws&newline != 0:
+		p.maybeIndentLine(ws)
+		p.writeByte('\n', 1)
+		p.spaceBefore = true
+	case ws&declcomma != 0:
+		p.writeByte(',', 1)
+		p.writeByte(' ', 1)
+		p.spaceBefore = true
+	case ws&noblank != 0:
+	case ws&vtab != 0:
+		p.writeByte('\v', 1)
+		p.spaceBefore = true
+	case ws&blank != 0:
+		p.writeByte(' ', 1)
+		p.spaceBefore = true
+	}
+}
+
+func (p *printer) markLineIndent(ws whiteSpace) {
+	p.indentStack = append(p.indentStack, ws)
+}
+
+func (p *printer) markUnindentLine() (wasUnindented bool) {
+	last := len(p.indentStack) - 1
+	if ws := p.indentStack[last]; ws&indented != 0 {
+		p.indent--
+		wasUnindented = true
+	}
+	p.indentStack = p.indentStack[:last]
+	return wasUnindented
+}
+
+func (p *printer) maybeIndentLine(ws whiteSpace) {
+	if ws&unindent == 0 && len(p.indentStack) > 0 {
+		last := len(p.indentStack) - 1
+		if ws := p.indentStack[last]; ws&indented != 0 || ws&indent == 0 {
+			return
+		}
+		p.indentStack[last] |= indented
+		p.indent++
+	}
+}
+
+func (f *formatter) matchUnindent() whiteSpace {
+	f.allowed |= unindent
+	// TODO: make this work. Whitespace from closing bracket should match that
+	// of opening if there is no position information.
+	// f.allowed &^= nooverride | newline | newsection | formfeed | blank | noblank
+	// ws := f.indentStack[len(f.indentStack)-1]
+	// mask := blank | noblank | vtab
+	// f.allowed |= unindent | blank | noblank
+	// if ws&newline != 0 || ws*indented != 0 {
+	// 	f.allowed |= newline
+	// }
+	return 0
+}
+
+// writeString writes the string s to p.output and updates p.pos, p.out,
+// and p.last. If isLit is set, s is escaped w/ tabwriter.Escape characters
+// to protect s from being interpreted by the tabwriter.
+//
+// Note: writeString is only used to write Go tokens, literals, and
+// comments, all of which must be written literally. Thus, it is correct
+// to always set isLit = true. However, setting it explicitly only when
+// needed (i.e., when we don't know that s contains no tabs or line breaks)
+// avoids processing extra escape characters and reduces run time of the
+// printer benchmark by up to 10%.
+//
+func (p *printer) writeString(s string, isLit bool) {
+	if s != "" {
+		p.spaceBefore = false
+	}
+
+	if isLit {
+		// Protect s such that is passes through the tabwriter
+		// unchanged. Note that valid Go programs cannot contain
+		// tabwriter.Escape bytes since they do not appear in legal
+		// UTF-8 sequences.
+		p.output = append(p.output, tabwriter.Escape)
+	}
+
+	if debug {
+		p.output = append(p.output, fmt.Sprintf("/*%s*/", p.pos)...) // do not update f.pos!
+	}
+	p.output = append(p.output, s...)
+
+	if isLit {
+		p.output = append(p.output, tabwriter.Escape)
+	}
+	// update positions
+	nLines := 0
+	var li int // index of last newline; valid if nLines > 0
+	for i := 0; i < len(s); i++ {
+		// CUE tokens cannot contain '\f' - no need to look for it
+		if s[i] == '\n' {
+			nLines++
+			li = i
+		}
+	}
+	p.pos.Offset += len(s)
+	if nLines > 0 {
+		p.pos.Line += nLines
+		c := len(s) - li
+		p.pos.Column = c
+	} else {
+		p.pos.Column += len(s)
+	}
+}
+
+func (p *printer) writeByte(ch byte, n int) {
+	for i := 0; i < n; i++ {
+		p.output = append(p.output, ch)
+	}
+
+	// update positions
+	p.pos.Offset += n
+	if ch == '\n' || ch == '\f' {
+		p.pos.Line += n
+		p.pos.Column = 1
+
+		n := p.cfg.Indent + p.indent // include base indentation
+		for i := 0; i < n; i++ {
+			p.output = append(p.output, '\t')
+		}
+
+		// update positions
+		p.pos.Offset += n
+		p.pos.Column += n
+
+		return
+	}
+	p.pos.Column += n
+}
+
+func mayCombine(prev, next token.Token) (before, after bool) {
+	s := next.String()
+	if 'a' <= s[0] && s[0] < 'z' {
+		return true, true
+	}
+	switch prev {
+	case token.IQUO, token.IREM, token.IDIV, token.IMOD:
+		return false, false
+	case token.INT:
+		before = next == token.PERIOD // 1.
+	case token.ADD:
+		before = s[0] == '+' // ++
+	case token.SUB:
+		before = s[0] == '-' // --
+	case token.QUO:
+		before = s[0] == '*' // /*
+	}
+	return false, false
+}
diff --git a/cue/format/testdata/comments.golden b/cue/format/testdata/comments.golden
new file mode 100644
index 0000000..53c3b01
--- /dev/null
+++ b/cue/format/testdata/comments.golden
@@ -0,0 +1,19 @@
+// Package line 1 group 1
+// Package line 2 group 1
+
+// Package line 1 - group 2
+// Package line 2 - group 2
+package comments
+
+// Emit line 1 group 1
+
+// Emit line 1 group 2
+// Emit line 2 group 2
+{
+	// Inside Emit
+}
+
+a: 3 // a line comment
+
+b:  4   // line comment that is last in the file.
+cc: 555 // align comments
diff --git a/cue/format/testdata/comments.input b/cue/format/testdata/comments.input
new file mode 100644
index 0000000..8aafcc9
--- /dev/null
+++ b/cue/format/testdata/comments.input
@@ -0,0 +1,19 @@
+// Package line 1 group 1
+// Package line 2 group 1
+
+// Package line 1 - group 2
+// Package line 2 - group 2
+package comments
+
+// Emit line 1 group 1
+
+// Emit line 1 group 2
+// Emit line 2 group 2
+{
+    // Inside Emit
+}
+
+a: 3 // a line comment
+
+b: 4 // line comment that is last in the file.
+cc: 555 // align comments
\ No newline at end of file
diff --git a/cue/format/testdata/expressions.golden b/cue/format/testdata/expressions.golden
new file mode 100644
index 0000000..deaf030
--- /dev/null
+++ b/cue/format/testdata/expressions.golden
@@ -0,0 +1,163 @@
+package expressions
+
+{
+	a:   1
+	aaa: 2
+
+	b: 3
+
+	c b a:    4
+	c bb aaa: 5
+	c b <Name> a: int
+	alias = 3.14
+
+	alias2 = foo
+	aaalias = foo
+	b: bar
+
+	bottom1: _|_
+	bottom2: _|_ // converted to compact symbol
+
+	empty: {}
+	emptyNewLine: {
+
+	}
+	someObject: {
+		a:   8
+		aa:  9
+		aaa: 10
+	}
+
+	e:  1 + 2*3
+	e:  1 * 2 * 3 // error
+	e:  2..3
+	e:  2..(3 + 4)
+	ex: 2..3 + 4*5
+	e:  (2..3)..4
+	e:  1 + 2 + 3 // error
+
+	e: s[1+2]
+	e: s[1:2]
+	e: s[1+2 : 2+4]
+	e: s[2]
+	e: s[2*3]
+	e: s[1+2*3]
+
+	e: f(3 + 4 + 5)
+	e: f(3 * 4 * 5)
+	e: f(3 + 4*5)
+
+	e: f(3 + 4 div 5)
+
+	e: 3 < 4 && 5 > 4
+	e: a || b && c || d
+
+	e: a + +b*3
+	e: -a - -b
+
+	e: b + c
+	e: b*c + d
+	e: a*b + c
+	e: a - b - c
+	e: a - (b - c)
+	e: a - b*c
+	e: a - (b * c)
+	e: a * b / c
+	e: a div b + 5
+	e: a / b
+	e: x[a | b]
+	e: x[a/b]
+	e: a & b
+	e: a + +b
+	e: a - -b
+	e: a div -b
+	e: x[a*-b]
+	e: x[a + +b]
+	e: len(longVariableName) * 2
+
+	e: "\(a)"
+	e: 'aa \(aaa) aa'
+	e: "aa \(aaa)"
+
+	e: [1, 2]
+	e: [1, 2, 3, 4,
+		5, 6, 7, 8]
+	e: [1, 2, 3, 4,
+		5, 6, 7, 8, // maybe force additional comma
+	]
+	e: [...]
+	e: [
+		...]
+	e: [...
+	]
+	e: [1, 2, ...]
+	e: [1, 2,
+		...]
+	e: [...int]
+	e: [...int]
+	e: [...int | float]
+	e: [ x for x in someObject if x > 9 ]
+	e: [ x
+		for x in someObject
+		if x > 9 ]
+	e: [ x
+		for x in someObject
+		if x > 9
+	]
+
+	[k]: v for k, v in someObject
+	[k]: v <-
+		for k, v in someObject
+
+	e: {[k]: v <-
+			for k, v in someObject
+			if k > "a"
+	}
+
+	e: {[k]: v for k, v in someObject if k > "a"}
+	e: {[k]: v <-
+			for k, v in someObject if k > "a"}
+
+	e: {[k]: v <-
+			for k, v in someObject
+			if k > "a"}
+
+	e: [{
+		a: 1, b: 2
+	}]
+
+	e: [{
+		a: 1, b: 2
+	},
+	]
+
+	e: [{
+		a: 1, b: 2
+	}, {
+		c: 1, d: 2
+	}]
+
+	e: [{
+		a: 1, b: 2
+	},
+		3,
+		4,
+	]
+
+	e: (a, b) -> a + b
+	e: (a,
+		b) -> a + b
+	e: (a, b) -> {
+		a: b
+	}
+	e: (a,
+		b,
+	) ->
+	{
+		a: b
+	}
+
+	e: e.f(1, 2)
+
+	e: (3 + 4)
+}
diff --git a/cue/format/testdata/expressions.input b/cue/format/testdata/expressions.input
new file mode 100644
index 0000000..0f104e0
--- /dev/null
+++ b/cue/format/testdata/expressions.input
@@ -0,0 +1,163 @@
+package expressions
+
+{
+    a: 1
+    aaa: 2
+
+    b: 3
+
+    c b a:  4
+    c bb aaa: 5
+    c b <Name> a: int
+    alias = 3.14
+
+    alias2 = foo
+    aaalias = foo
+    b: bar
+
+    bottom1: _|_
+    bottom2: _|_ // converted to compact symbol
+
+    empty: {}
+    emptyNewLine: {
+
+    }
+    someObject: {
+        a: 8
+        aa: 9
+        aaa: 10
+    }
+
+    e: 1+2*3
+    e: 1*2*3 // error
+    e: 2..3
+    e: 2..(3 + 4)
+    ex: 2..3+4*5
+    e: 2..3..4
+    e: 1 + 2 + 3 // error
+
+    e: s[1+2]
+    e: s[1:2]
+    e: s[1+2:2+4]
+    e: s[2]
+    e: s[2*3]
+    e: s[1+2*3]
+
+    e: f(3+4+5)
+    e: f(3*4*5)
+    e: f(3+4*5)
+
+    e: f(3 + 4 div 5)
+
+    e: 3<4&&5>4
+    e: a || b && c || d
+
+    e: a + +b * 3
+    e: -a - -b
+
+    e: b + c
+    e: b*c + d
+    e: a*b + c
+    e: a - b - c
+    e: a - (b - c)
+    e: a - b*c
+    e: a - (b * c)
+    e: a * b / c
+    e: a div b + 5
+    e: a / b
+    e: x[a|b]
+    e: x[a /b]
+    e: a & b
+    e: a + +b
+    e: a - -b
+    e: a div - b
+    e: x[a*-b]
+    e: x[a + +b]
+    e: len(longVariableName) * 2
+
+    e: "\(a)"
+    e: 'aa \(aaa) aa'
+    e: "aa \(aaa)"
+
+    e: [1, 2]
+       e: [1, 2, 3, 4,
+    5, 6, 7, 8]
+    e: [1, 2, 3, 4,
+    5, 6, 7, 8 // maybe force additional comma
+    ]
+    e: [...]
+    e: [
+        ...]
+    e: [...
+    ]
+    e: [1, 2, ...]
+    e: [1, 2,
+    ...]
+    e: [...int]
+    e: [...int,]
+    e: [...int | float]
+    e: [ x for x in someObject if x > 9 ]
+    e: [ x
+    for x in someObject
+    if x > 9 ]
+   e: [ x
+    for x in someObject
+    if x > 9
+    ]
+
+    [k]: v for k, v in someObject
+    [k]: v <-
+    for k, v in someObject
+
+    e: { [k]:v <-
+    for k, v in someObject
+    if k > "a"
+    }
+
+    e: { [k]:v for k, v in someObject if k > "a"}
+    e: { [k]:v <-
+    for k, v in someObject if k > "a"}
+    
+    e: { [k]:v <-
+    for k, v in someObject
+    if k > "a"}
+
+    e: [{
+        a: 1, b: 2,
+    }]
+
+    e: [{
+        a: 1, b: 2,
+    },
+    ]
+
+   e: [{
+        a: 1, b: 2,
+    }, {
+        c: 1, d: 2,
+    }]
+
+    e: [{
+        a: 1, b: 2,
+    },
+        3,
+        4,
+    ]
+
+    e: (a, b) -> a + b
+    e: (a,
+    b) -> a + b
+    e: (a, b) -> {
+        a:  b
+    }
+    e: (a,
+     b
+     ) ->
+    {
+        a:  b
+    }
+
+    e: e.f(1, 2)
+
+    e: ((3 + 4))
+}
\ No newline at end of file
diff --git a/cue/format/testdata/simplify.golden b/cue/format/testdata/simplify.golden
new file mode 100644
index 0000000..edb8b27
--- /dev/null
+++ b/cue/format/testdata/simplify.golden
@@ -0,0 +1,6 @@
+
+foo bar: "str"
+
+a B: 42
+
+"a.b" "foo-" cc_dd: x
diff --git a/cue/format/testdata/simplify.input b/cue/format/testdata/simplify.input
new file mode 100644
index 0000000..08e374d
--- /dev/null
+++ b/cue/format/testdata/simplify.input
@@ -0,0 +1,5 @@
+"foo" "bar": "str"
+
+a "B": 42
+
+"a.b" "foo-" "cc_dd": x
\ No newline at end of file