Here's something that was idling in my code attic for quite a while and finally 
got pushed past the "is it useful yet" line this weekend: a source code 
formatter for Nim!

Apart from a working package manager, I can't really think of a tool that I've 
missed more than this on a daily basis from other languages, so without further 
ado: <https://github.com/arnetheduck/nph/>.

**I intend to never ever format Nim code manually again - it is an utterly 
useless waste of time**

`nph` takes the opinionated route of discarding existing formatting and 
normalizing all code to a single, beautiful style which loosely is based on 
already-existing formatters for other languages such as 
[`black`](https://github.com/psf/black) and [`prettier`](https://prettier.io/).

The formatter parses the input using a somewhat customized parser based on the 
one that ships with the compiler, creates a somewhat customized AST that 
retains a bit more information about the original source code than a typical 
AST, then writes it back best it can.

Before writing the changes to disk, the original compiler parser is called on 
both the unformatted and formatted code, comparing the two AST:s for semantic 
differences - if there is any difference, the original code is left untouched 
thus fulfilling the first rule of formatters: thou shalt not break working code.

If everything goes well, the formatted code is written to disk and you can get 
on with your life :)

To get an idea what the format looks like, here's a typical `proc` definition - 
everything fits on one line, nice!:
    
    
    proc covers(entry: AttestationEntry; bits: CommitteeValidatorsBits): bool =
      ...
    
    
    Run

If we add more arguments, it starts getting long - `nph` will try with a 
version where the arguments sit on a line of their own:
    
    
    proc addAttestation(
        entry: var AttestationEntry; attestation: Attestation; signature: 
CookedSig
    ): bool =
      ...
    
    
    Run

The return type was given its own line which makes it easy to pick out as the 
function definition grows - it stays aligned with `proc` however so you can 
find arguments, return type and actual implementation at a glance.

Some procedures take even more space - specially when using descriptive names, 
libraries like [`Result`](https://github.com/arnetheduck/nim-results) or macros 
like`async` - such a signature provides a lot of useful information to both the 
compiler and the programmer and `nph` will break it down for you to make it 
easy to grok:
    
    
    proc validateBlsToExecutionChange*(
        pool: ValidatorChangePool;
        batchCrypto: ref BatchCrypto;
        signed_address_change: SignedBLSToExecutionChange;
        wallEpoch: Epoch
    ): Future[Result[void, ValidationError]] {.async.} =
      ...
    
    
    Run

The above idea extends to most formatting: if something is simple, format it in 
a simple way - if not, use a bit of style to break down what's going on into 
more easily consumable pieces - here's a function call:
    
    
    let res =
      check_bls_to_execution_change(
        pool.dag.cfg.genesisFork,
        forkyState.data,
        signed_address_change,
        {skipBlsValidation}
      )
    
    
    Run

`nph` is formatted with itself and comes with a bunch of before/after examples 
used for testing, which certainly could be extended. Some of the tests cover 
cases where there's still room for improvement (of which there is plenty) - 
comments in particular are tricky and might get moved to slightly unexpected 
places if they weren't placed in a location recognised by the `nph` heuristics.

Here's the Nim compiler, formatted with `nph`: 
<https://github.com/arnetheduck/Nim/commit/75bdacdd4a3f829c0f1fe149673c2afd6add55a2>

The README has a section on frequently asked questions that nobody yet has 
asked as well as all information you could possibly want in terms of how to run 
it - have a look and let me know what you think!

P.S. the name nph is a last-minute change after yardanico alerted me that 
`nimph`, the original name I had used, [was 
taken](https://github.com/disruptek/nimph) and by nothing less than a package 
manager! I knew I should have published it when I started - now, I'm taking 
naming suggestions. 

Reply via email to