wuffs: github.com/google/wuffs/lib/dumbindent Index | Examples | Files

package dumbindent

import "github.com/google/wuffs/lib/dumbindent"

Package dumbindent formats C (and C-like) programs.

It is similar in concept to pretty-printers like `indent` or `clang-format`. It is much dumber (it will not add or remove line breaks or otherwise re-flow lines of code just to fit within an 80 column limit) but it can therefore be much faster at the basic task of automatically indenting nested blocks. The output isn't 'perfect', but it's usually sufficiently readable if the input already has sensible line breaks.

To quantify "much faster", on this one C file, `cmd/dumbindent` in this repository was 80 times faster than `clang-format`, even without a column limit:

$ wc release/c/wuffs-v0.2.c
 11858  35980 431885 release/c/wuffs-v0.2.c
$ time dumbindent                               < release/c/wuffs-v0.2.c > /dev/null
real    0m0.008s
user    0m0.005s
sys     0m0.005s
$ time clang-format-9                           < release/c/wuffs-v0.2.c > /dev/null
real    0m0.668s
user    0m0.618s
sys     0m0.032s
$ time clang-format-9 -style='{ColumnLimit: 0}' < release/c/wuffs-v0.2.c > /dev/null
real    0m0.641s
user    0m0.585s
sys     0m0.037s

Apart from some rare and largely uninteresting exceptions, the dumbindent algorithm only considers:

∙ '{' and '}' curly braces,
∙ '(' and ')' round parentheses,
∙ '\n' line breaks,
∙ ' ' spaces and '\t' tabs that start or end a line, and
∙ strings, comments and preprocessor directives (in order to ignore any of
  the above special characters within them),

Everything else is an opaque byte. Consider this input:

for (i = 0; i < 3; i++) {
j = 0;  // Ignore { in a comment.
if (i < j) { foo(); }
u = (v +
w);
}

From the algorithm's point of view, this input is equivalent to:

....(.................).{
.................................
...(.....).{....()..}
....(...
.);
}

The formatted output (using the default of 2 spaces per indent level) is:

....(.................).{
  .................................
  ...(.....).{....()..}
  ....(...
      .);
}

Dumbindent adjusts lines horizontally (indenting) but not vertically (it does not break or un-break lines, or collapse consecutive blank lines), although it will remove blank lines at the end of the input. In the example above, it will not remove the "\n" between "u = (v +" and "w);", even though both lines are short.

Each output line is indented according to the net number of open braces preceding it, although lines starting with close braces will outdent first, similar to `gofmt` style. A line which starts in a so-far-unbalanced open parenthesis, such as the "w);" line above, gets 2 additional indent levels.

Horizontal adjustment only affects a line's leading white space (and will trim trailing white space). It does not affect white space within a line. Dumbindent does not parse the input as C/C++ source code.

In particular, the algorithm does not solve C++'s "most vexing parse" or otherwise determine whether "x*y" is a multiplication or a type definition (where y is a pointer-to-x typed variable or function argument, such as "int*p"). For a type definition, where other formatting algorithms would re-write around the "*" as either "x* y" or "x *y", dumbindent will not insert spaces.

Similarly, dumbindent will not correct this mis-indentation:

if (condition)
  goto fail;
  goto fail;

Instead, when automatically or manually generating the input for dumbindent, it is recommended to always emit curly braces (again, similar to `gofmt` style), even for what would otherwise be 'one-liner' if statements.

More commentary is at: https://nigeltao.github.io/blog/2020/dumbindent.html

Index

Examples

Package Files

dumbindent.go

func FormatBytes Uses

func FormatBytes(dst []byte, src []byte, opts *Options) []byte

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 unused capacity, cap(dst) - len(dst), is too short to hold the formatted program. In this case, a new slice will be allocated and returned.

Passing a nil opts is valid and equivalent to passing &Options{}.

Code:

const src = `
for (i = 0; i < 3; i++) {
j = 0;  // Ignore { in a comment.
if (i < j) { foo(); }
u = (v +
w);
}
`

os.Stdout.Write(FormatBytes(nil, []byte(src), nil))

Output:

for (i = 0; i < 3; i++) {
  j = 0;  // Ignore { in a comment.
  if (i < j) { foo(); }
  u = (v +
      w);
}

type Options Uses

type Options struct {
    // Spaces, if positive, is the number of spaces per indent level. A
    // non-positive value means to use the default: 2 spaces per indent level.
    //
    // This field is ignored when Tabs is true.
    Spaces int

    // Tabs is whether to indent with tabs instead of spaces. If true, it's one
    // '\t' tab character per indent and the Spaces field is ignored.
    Tabs bool
}

Options are formatting options.

Package dumbindent imports 1 packages (graph) and is imported by 2 packages. Updated 2020-07-15. Refresh now. Tools for package owners.