3* [Analysis of the existing solutions](#analysis-of-the-existing-solutions)
4 * [Brittany](#brittany)
6 * [Stylish Haskell](#stylish-haskell)
7 * [Haskell formatter](#haskell-formatter)
8* [Proposed design](#proposed-design)
11 * [Printing](#printing)
12 * [Configuration](#configuration)
13 * [Handling of language extensions](#handling-of-language-extensions)
15 * [Functionality of executable](#functionality-of-executable)
16 * [Why not contribute to/fork Hindent or Brittany?](#why-not-contribute-tofork-hindent-or-brittany)
19This document describes design of a new formatter for Haskell source code.
20It also includes recommendations for future implementers.
22We set for the following goals (mostly taken from
23[brittany](https://github.com/lspitzner/brittany)):
24* Preserve the meaning of the formatted functions;
25* Make reasonable use of screen space;
26* Use linear space and computation time on the size of the input;
30## Analysis of the existing solutions
32In order to design a new formatter we need to study the existing solutions
33so we can borrow the good bits and avoid making the same mistakes.
37[Brittany][brittany] builds on top of [`ghc-exactprint`][ghc-exactprint]—a
38library that uses parser of GHC itself for parsing and thus it guarantees
39that at least parsing phase is bug-free (which is admittedly the cause of
40majority of bugs in other projects, see below).
42After parsing, Haskell AST and a collection of annotations are available.
43The annotations are there because Haskell AST doesn't provide enough
44information to reconstruct source code (for example it doesn't include
45comments). The AST and the annotations are converted into a `BriDoc` value.
46A `BriDoc` value is a document representation like the `Doc` from the
47[pretty][pretty-doc] or the [wl-pprint][wl-pprint-doc] libraries.
49Brittany implements its own document type in an attempt to find a
50satisfactory rendering of the source code that fits a page-width
51constraint. Because of this, a `BriDoc` value represents a collection
52of many candidate layouts (i.e. renderings) of the source code.
54This collection is pruned until it contains a single layout. The
55structure of the chosen layout is then adjusted to leave it in a
56form which can be easily traversed to produce the final rendering.
58Brittany invests the majority of its implementation to manage
59the `BriDoc` values. Given that the amount of possible layouts is
60exponential, the representation is clever enough to fit them in
61linear space. There are multiple ways to build a `BriDoc`, not all
62of which fit in linear space. So care is necessary to keep memory
65The compexities of the `BriDoc` structure, together with the lack of
66documentation, make Brittany at least challenging to maintain.
70[Hindent][hindent] uses [`haskell-src-exts`][haskell-src-exts] for parsing
71like all older projects. `haskell-src-exts` does not use parser of GHC
72itself, and is a source of endless parsing bugs. `Hindent` is affected by
73these upstream issues as well as Stylish Haskell and Haskell formatter (see
74below). This already makes all these projects unusable with some valid
75Haskell source code, but let's continue studying Hindent anyway.
77Hindent is quite different from Brittany in the sense that it does not
78attempt to build a document representation to render
79afterwards, instead it just prints the parsed code straight away. This means
80that the 70-80% of what the code does is a printing traversal.
82Hindent code is easier to read and debug. Pretty-printing functions are
83very straightforward. If there is a bug (in pretty-printer, not in parser
84which Hindent cannot control), it's easy to fix AFAIU.
86Hindent is also notable for its ability to handle CPP and inputs that do not
87constitute complete modules. It splits input stream into so-called “code
88blocks” recognizing CPP macros and then only pretty-prints “normal code”
89without touching CPP directives. After that CPP is inserted between
90pretty-printed blocks of source code. The approach fails when CPP breaks
91code in such a way that separate blocks do not form valid Haskell
93[this](https://github.com/commercialhaskell/hindent/issues/383) for example.
95Looking at the bug tracker there are many bugs. Part of them is because of
96the use of `haskell-src-exts`, the other part is because the maintainer
97doesn't care (anymore?) and doesn't fix them. Well it's as simple as that,
98with any sort of commercial backing the bugs in pretty printer would be
103[Stylish Haskell][stylish-haskell] also uses `haskell-src-exts` and suffers
104from the same upstream problems. I haven't studied the transformations it
105performs, but it looks like it transforms the parsed source code partially
106by manipulating AST and partially by manipulating raw text (e.g. to drop
107trailing whitspace from each line). CPP Macros are just filtered out
108silently as a preprocessing step before feeding the code to
111Stylish Haskell is not so invasive as the other formatters and most reported
112bugs are about parsing issues and CPP. As I understand it, people mostly use
113it to screw their import lists.
117[Haskell formatter][haskell-formatter] is an older project that didn't get
118much traction. It also uses `haskell-src-ext` and also tries to do
119manipulations on the parsed AST. The issue tracker doesn't have many issues
120probably because it never got popular enough (only 15 stars on GitHub). All
121the issues are about upstream problems with `haskell-src-exts`.
125This section describes a solution that combines all the good things from the
130It is clear that `ghc-exactprint` is better than `haskell-src-exts`, so we
131should use that. If we go with `ghc-exactprint` though, we'll need to
132specify which parser to use, e.g. the parser that parses whole module or the
133one which parsers declarations/expressions/etc. It seems that in general
134it's better to use the parser for modules because it should work with all
135input files containing complete modules, while with other parsers it's
136impossible to guess what they'll be called on.
140Formatting a module which uses CPP directives won't be supported. Instead,
141we hope for a solution to replace CPP to do conditional compilation.
143There are the following challenges when formatting a module with CPP:
145* GHC parser won't accept anything but a valid, complete module. Therefore,
146 formatting the Haskell code between CPP directives is not an option.
148* Ignoring the CPP directives and formatting the Haskell code can change
149 its meaning. An example follows.
151Let's suppose that we want to format the following program:
156main = print (g && f1)
179At the time of this writing, formatting this program with Hindent
180produces the same output we would get if the CPP directives were
192main = print (g && f1)
215Running the formatter causes the output of the program to change
216from `True` to `False` when `C1` is not defined.
218A solution could be to make the formatter more careful with CPP
219directives, constraining how directives can be inserted in Haskell
220code to avoid changing the meaning by reformatting. But
221this would introduce additional complexity, and the problem would
222need to be solved repeteadly for every tool out there which wants
223to parse Haskell modules. If CPP is replaced with some language
224extension or mechanism to do conditional compilation, all tools
227Therefore, CPP won't be supported. If the CPP extension
228is enabled, we should signal an error right away.
232Just pretty-printing code (following the approach of Hindent) seems sane. It
233is straightforward and when complemented with enough tests (see the section
234about testing below), it should work all right.
236Implementation can be just a `Reader` monad producing something like text
237builder. The context of `Reader` can store current indentation and
238configuration options.
240As the pretty-printing library we can use [`Outputable`][outputable] (and
241`SDoc`) from the [`ghc`][ghc] package itself (at least for pretty-printing
242basic things like floating point literals and the like). The benefit is that
243AST components that we'll want to print are already instances of
244`Outputable`, so we'll get correct renderings for free.
246In order to keep the output of the formatter simple, fast and correct,
247we introduce the following rule. The pretty-printing code can be in
248control of every formatting choice, except for two, which are left to
2511. location of comments (comments are going to be attached to specific
252 syntactic entities, so moving an entity will move its comment too),
255Regarding (2), the idea is that given any syntactic entity, the programmer
2581. write it on one line, or
2592. write it on two lines or more.
261If (1), then everything is kept in one line. If (2), i.e. a line break appears
262somewhere in the concrete syntax tree (CST), then additional line breaks are
263introduced everywhere possible in parent nodes, *but not in any sibling or
280map :: (a -> b) -> [a] -> [b]
282foldr :: (a -> b -> b) ->
303Crucially, no effort is made to fit within reasonable line lengths. That's
304up to the programmer. Style will still be consistent for everyone in every
305other aspect, and that's what counts.
309We are not allowing to configure any aspect of the formatter. A module
310might be used in multiple projects, and we prefer to have it formatted
311the same in all of them.
314post][hindent-5-blog] by Chris Done (the author of Hindent) which says that
315as long as the default style is conventional and good it doesn't really
316matter how code gets formatted. Consistency is more important.
318### Handling of language extensions
320Some language extensions affect how parsing is done. We are going to deal
321with those in two ways:
323* When language pragmas are present in source file, we must parse them
324 before we run the main parser (I guess) and they should determine how the
325 main parsing will be done.
326* There also should be configuration file that may enable other language
327 extensions to be used on all files.
328* Later we could try to locate Cabal files and fetch the list of extensions
329 that are enabled by default from there.
333It should be possible to add tests incrementally as we develop
334pretty-printing code and new issues are discovered. For each Haskell
335module that we want to test, we perform the following steps:
3371. Given input snippet of source code parse it and pretty print it.
3382. Parse the result of pretty-printing again and make sure that AST is the
339 same as AST of original snippet module span positions. We could make
340 this part of a self-check in the formatter.
3413. Check the output against expected output. Thus all tests should include
342 two files: input and expected output.
3434. Check that running the formatter on the output produces the same output
344 again (the transformation is idempotent).
346In order to grow our testsuite, we would borrow test cases from test
347suites of existing libraries like Brittany and Hindent.
348Then we may add test cases for opened issues that Brittany and Hindent have.
350When we're confident enough, we can start “mining” new issues by running
351the program on real source code from Hackage till we don't get new issues
352anymore. For every issue that we find this way, a test case should be added.
354### Functionality of executable
356* In all cases the program should test if the produced AST is the same as
357 the one we originally parsed and if it differs, an error should be
358 displayed suggesting reporting this on our issue tracker.
359* Check mode: return non-zero exit code if any transformations would be
361* Modification in place and printing of formatted code to stdout.
362* A flag for version/commit information.
363* An option to specify location of config file.
364* Options to specify parameters that come from config files on command line
365 instead (currently this is just dynamic options enabled by default, such
366 as langauge extensions).
368### Why not contribute to/fork HIndent or Brittany?
370We want to simultaneously optimize three goals:
3721. simplicity of implementation,
3743. predictable and readable output that doesn't overuse vertical spacing.
376Hindent aims for (1) and (2) by still producing something palatable in (3).
378(1) but goes a long way towards (3) and presumably does OK on (2). Ormolu
379goes for (1), (2) and (3), by outsourcing the hard part of (3) to the user.
380Ormolu is less normative than Brittany, and less normative than Hindent,
381but arguably stills achieves consistent style.
383Forking or contributing to Hindent is not an option because if we replace
384`haskell-src-exts` with `ghc` (or `ghc-exact-print`) then we'll have to work
385with a different AST type and all the code in Hindent will become
386incompatible and there won't be much code to be re-used in that case. It is
387also possible that we'll find a nicer way to write pretty-printer.
391A list of formatting examples can be found [here](EXAMPLES.md).
395Proposed roadmap (for a single person, about 39 full-time work days):
397* ~~Create and setup a repo, setup CI (less than 1 day).~~
398* ~~Implement parsing (2 days).~~
399* ~~Implement basis for pretty-printing of code. We could take
400 [this][hindent-printer] as inspiration, although AST from the `ghc`
401 package may be slightly different. Also, don't forget about annotations
402 and comments (1 week).~~
403* Implement executable program so we can easier fool around. Try it on
404 simple code samples (2 days).
405* Implement location and reading of configuration file in YAML format (2
407* Continue writing the pretty-printing code till we cover everything. Start
408 adding tests, see [the section about testing](#testing). Fix bugs. At this
409 point just repeat this till we cover everything that Brittany and Hindent
410 can do maybe by stealing their tests (at least 2 weeks).
411* Start checking how our formatter works on real-world code from Hackage.
412 Fix bugs that we find that way (2 weeks).
413* Write a good readme explaining why the project was created and how it is
414 better, etc (2 days).
415* Switch CI to Travis and test with multiple GHC versions (1 day).
416* Make and announce the first public release on Twitter/Reddit (1 day).
417* Figure out how to use the formatter application with major text editors
418 and add the info to the readme (no estimate).
420[brittany]: https://hackage.haskell.org/package/brittany
421[hindent]: https://hackage.haskell.org/package/hindent
422[hindent-5-blog]: https://chrisdone.com/posts/hindent-5
423[stylish-haskell]: https://hackage.haskell.org/package/stylish-haskell
424[haskell-formatter]: https://hackage.haskell.org/package/haskell-formatter
425[ghc]: https://hackage.haskell.org/package/ghc
426[outputable]: https://hackage.haskell.org/package/ghc-8.4.3/docs/Outputable.html
427[haskell-src-exts]: https://hackage.haskell.org/package/haskell-src-exts
428[ghc-exactprint]: https://hackage.haskell.org/package/ghc-exactprint
429[hindent-printer]: https://github.com/commercialhaskell/hindent/blob/master/src/HIndent/Pretty.hs
430[pretty-doc]: http://hackage.haskell.org/package/pretty-1.1.3.6/docs/Text-PrettyPrint.html#t:Doc
431[wl-pprint-doc]: http://hackage.haskell.org/package/wl-pprint-1.2.1/docs/Text-PrettyPrint-Leijen.html#t:Doc
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list