privat/nit — pull request #645 by Morriar (2014-08-05 18:51:56+00:00)
Title: Introduce `nitpretty`: a pretty printer for nit source files.

# Commit summary

First commit introduce the tool (formated with nitpretty).
Second commit add tests for nitpretty.
Last two commits propose an update of lib files formatted with nitpretty.

# Tool Description

`nitpretty` is a tool able to pretty print Nit files.

Usage:

$ nitpretty source.nit

Main options:

* `-o res.nit` output result into `res.nit`
* `--diff` show diff between `source` and `res`
* `--meld` open diff with `meld`
* `--check` check the format of multiple source files
* `--check --meld` perform `--check` and open `meld` for each difference

## Specification

The specification of the pretty printing is described here.

* Default indentation level is one `'\t'` character and
is increased by one for each indentation level.
* Default line max-size is 80.

### Comments

There is many categories of comments:

`Licence comments` are attached to the top of the file
no blank line before, one after.

# This is a licence comment

# Documentation for module `foo`
module foo

`ADoc` are documentation comments attached to a `AModule`, `AClassdef`, `APropdef`.

They are printed before the definition with a blank line before and no after
at the same indentation level than the definition.

# Documentation for module `foo`
module foo

# Documentation for class `Bar`
class Bar
# Documentation for method `baz`
fun baz do end
end

`Block comments` are comments composed of one or more line rattached to nothing.
They are displayed with one blank line before and after at current indent level.

# block
# comment

`Attached comments` are comments attached to a production.
They are printed as this.

fun foo do # attached comment
end

`nitpretty` automatically remove multiple blanks between comments:

# Licence
# ...

# Block comment

### Inlinging

Productions are automatically inlined when possible.

Conditions:

* the production must be syntactically inlinable
* the inlined production length is less than `PrettyPrinterVisitor::max-size`
* the production do not contains any comments

### Modules

* There is a blank between the module declaration and its imports
* There is no blank between imports and only one after
* There is a blank between each exten block definition
* There is a blank between each class definition
* There is always a blank line at the end of the module

Example:

# Documentation for module `foo`
module foo

import a
# import b
import c

# Documentation for class `Bar`
class Bar end

class Baz end # not a `ADoc` comment

### Classes

* There is no blank between the class definition and its super-classes declarations
* There is no blank between two inlined property definition
* There is a blank between each block definition
* There no blank line at the end of the class definition

Example:

# Documentation for class `Bar`
class Bar end

class Baz
super Bar

fun a is abstract
private fun b do end

fun c do
# ...
end
end

Generic types have no espace after or before brackets and are separated by a comma and a space:

class A[E: Type1, F: Type1] do end

### Blocks

* Inlined productions have no blank lines between them
* Block productions have a blank before and after

var a = 10
var b = 0

if a > b then
# is positive
print "positive"
end

print "end"

### Calls and Binary Ops

Arguments are always printed separated with a comma and a space:

foo(a, b, c)

Binary ops are always printed wrapped with spaces:

var c = 1 + 2

Calls and binary ops can be splitted to feat the `max-size` constraint.
Breaking priority is given to arguments declaration after the comma.

return foo("aaaaaaaaaaaaaaaaaaaaaaaaaa", "bbbbbbbbbbbbbbbbbbbbbbbbbbb",
"cccccccccccccccccccccccccc")

Binary ops can also be broken to feet the `maz-size` limit:

return "aaaaaaaaaaaaaaaaaaaaaaaaaa" + "bbbbbbbbbbbbbbbbbbbbbbbbbbb" +
"cccccccccccccccccccccccccc"

# Fiability of the tool

I tested it on lib/ and verified manually all the changes. The result displayed in the two lasts commits contains no manual transformation (except to adjust some misplaced comments).

I guess that some constructs are not treated as well as they can, I need some user feedback.

There maybe still cases in src/ that are not printed without some bugs so I will not say that it's safe to print and replace files you are working on and have no copy of.

How to stay safe? Simple, commit before applying pretty printing and check the diff after or use options `--diff` or `--meld` to apply changes manually.

I used the option combination `--check --meld` to check and edit all the lib/ files one by one. It was surprinsingly fast but boring...

I also ran the tool on src/ and it ended up with no failures but there still formatting to update, I propose the lazy way: futures users may apply the formatter before commiting files.

# TODO

It can be cool to integrate this with vim or a Nit IDE.
It can also be integrated with some tests hooks before merging.

The `collect_length` calculation is based on `Token::collect_text` so the length is based on the tokens as they are written and not how they will be trasnformed. Furthermore, possibles spaces or trailing comments are not taken into account.

This have two impacts:

* some lines are not break at 80 chars and may overflow by a few chars
* when appliying the pretty printer on an already pretty printed file, some new transformation can append (because some `end` tokens have been removed by the previous execution and production is now shorter and can be inlined).

Possible fixes:

* ran the tool while the fix point if not reached :p
* redefines the `collect_length` method for all production to take spaces into accounts
* pre-do a transformation, test the length of the result then apply the inlining

All but first, can be long to implement for the possibles outcomes so I forward this to future-me "if needed later".