Have dumbindent take a dst slice argument
diff --git a/cmd/dumbindent/main.go b/cmd/dumbindent/main.go
index 869fc07..fda4103 100644
--- a/cmd/dumbindent/main.go
+++ b/cmd/dumbindent/main.go
@@ -145,7 +145,7 @@
 		return err
 	}
 
-	dst, err := dumbindent.Format(src)
+	dst, err := dumbindent.FormatBytes(nil, src)
 	if err != nil {
 		return err
 	}
diff --git a/internal/cgen/cgen.go b/internal/cgen/cgen.go
index fdb7043..1abce1e 100644
--- a/internal/cgen/cgen.go
+++ b/internal/cgen/cgen.go
@@ -182,7 +182,7 @@
 			return unformatted, nil
 		}
 
-		return dumbindent.Format(unformatted)
+		return dumbindent.FormatBytes(nil, unformatted)
 	})
 }
 
diff --git a/lib/dumbindent/dumbindent.go b/lib/dumbindent/dumbindent.go
index e92b6fb..fbdd1f9 100644
--- a/lib/dumbindent/dumbindent.go
+++ b/lib/dumbindent/dumbindent.go
@@ -43,24 +43,31 @@
 	spaces  = []byte("                                ")
 )
 
-// Global state.
-var (
-	nBraces   int  // The number of unbalanced '{'s.
-	nParens   int  // The number of unbalanced '('s.
-	openBrace bool // Whether the previous line ends with '{'.
-	hanging   bool // Whether the previous line ends with '=' or '\\'.
-)
-
-// hangingBytes is a look-up table for updating the hanging global variable.
+// hangingBytes is a look-up table for updating the hanging variable.
 var hangingBytes = [256]bool{
 	'=':  true,
 	'\\': true,
 }
 
-// Format formats the C (or C-like) program in src.
-func Format(src []byte) (dst []byte, retErr error) {
-	dst = make([]byte, 0, len(src)+(len(src)/2))
-	blankLine := false
+// FormatBytes formats the C (or C-like) program in src, appending the result
+// to dst, and returns that longer slice.
+//
+// It is valid to pass a dst slice (such as nil) whose spare capacity (not
+// including its existing elements) is too short to hold the formatted program.
+// In this case, a new slice will be allocated and returned.
+func FormatBytes(dst []byte, src []byte) ([]byte, error) {
+	if len(src) == 0 {
+		return dst, nil
+	} else if len(dst) == 0 {
+		dst = make([]byte, 0, len(src)+(len(src)/2))
+	}
+
+	nBraces := 0       // The number of unbalanced '{'s.
+	nParens := 0       // The number of unbalanced '('s.
+	openBrace := false // Whether the previous non-blank line ends with '{'.
+	hanging := false   // Whether the previous non-blank line ends with '=' or '\\'.
+	blankLine := false // Whether the previous line was blank.
+
 	for line, remaining := src, []byte(nil); len(src) > 0; src = remaining {
 		line, remaining = src, nil
 		if i := bytes.IndexByte(line, '\n'); i >= 0 {
@@ -106,7 +113,7 @@
 		nBraces -= closeBraces
 
 		// When debugging, uncomment these (and import "strconv") to prefix
-		// every non-blank line with the global state.
+		// every non-blank line with the state.
 		//
 		// dst = append(dst, strconv.Itoa(nBraces)...)
 		// dst = append(dst, ',')
@@ -147,8 +154,8 @@
 		dst = append(dst, line...)
 		dst = append(dst, "\n"...)
 
-		// Adjust the global state according to the braces and parentheses
-		// within the line (except for those in comments and strings).
+		// Adjust the state according to the braces and parentheses within the
+		// line (except for those in comments and strings).
 		last := lastNonWhiteSpace(line)
 	loop:
 		for s := line[closeBraces:]; ; {
diff --git a/lib/dumbindent/dumbindent_test.go b/lib/dumbindent/dumbindent_test.go
new file mode 100644
index 0000000..d788602
--- /dev/null
+++ b/lib/dumbindent/dumbindent_test.go
@@ -0,0 +1,66 @@
+// Copyright 2020 The Wuffs 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
+//
+//    https://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 dumbindent
+
+import (
+	"testing"
+)
+
+func TestFormatBytes(tt *testing.T) {
+	testCases := []struct {
+		src  string
+		want string
+	}{{
+		// Braces.
+		src:  "foo{\nbar\n    }\nbaz\n",
+		want: "foo{\n  bar\n}\nbaz\n",
+	}, {
+		// Parentheses.
+		src:  "i = (j +\nk)\np = q\n",
+		want: "i = (j +\n    k)\np = q\n",
+	}, {
+		// Hanging assignment.
+		src:  "int x =\ny;\n",
+		want: "int x =\n    y;\n",
+	}, {
+		// Hanging assignment with slash-slash comment.
+		src:  "int x = // comm.\ny;\n",
+		want: "int x = // comm.\n    y;\n",
+	}, {
+		// Most blank lines should be dropped.
+		src:  "f {\n\n\ng;\n\n\nh;\n\n\n}\n\n",
+		want: "f {\n  g;\n\n  h;\n}\n",
+	}, {
+		// Single-quote string.
+		src:  "a = '{'\nb = {\nc = 0\n",
+		want: "a = '{'\nb = {\n  c = 0\n",
+	}, {
+		// Double-quote string.
+		src:  "a = \"{\"\nb = {\nc = 0\n",
+		want: "a = \"{\"\nb = {\n  c = 0\n",
+	}, {
+		// Label.
+		src:  "if (b) {\nlabel:\nswitch (i) {\ncase 0:\nj = k\nbreak;\n}\n}\n",
+		want: "if (b) {\nlabel:\n  switch (i) {\n    case 0:\n    j = k\n    break;\n  }\n}\n",
+	}}
+
+	for i, tc := range testCases {
+		if g, err := FormatBytes(nil, []byte(tc.src)); err != nil {
+			tt.Fatalf("i=%d, src=%q: %v", i, tc.src, err)
+		} else if got := string(g); got != tc.want {
+			tt.Fatalf("i=%d, src=%q:\ngot  %q\nwant %q", i, tc.src, got, tc.want)
+		}
+	}
+}