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)
+ }
+ }
+}