Skip to content

Implement formatting logic (without comments/linebreak support)#117

Merged
robinheghan merged 39 commits intogren-lang:mainfrom
avh4:format
Sep 9, 2022
Merged

Implement formatting logic (without comments/linebreak support)#117
robinheghan merged 39 commits intogren-lang:mainfrom
avh4:format

Conversation

@avh4
Copy link
Copy Markdown
Contributor

@avh4 avh4 commented Aug 27, 2022

This is the base formatting logic. This is one part of #42. Handling non-documentation comments and linebreak sensitivity are not part of this PR.

Run with gren format from inside a project with a gren.json, or run gren format <files and/or directories...>.

This is based on the learnings from elm-format, but is rewritten and greatly simplified, and also should be more performant.

TODO:

  • stuff that's already finished when I wrote the todo list
  • record extension types
  • port modules and ports
  • doc comments
  • binary operator declarations
  • fix string literal escaping
  • keep nested function type indentation to one level
  • correct spacing in exposing listings
  • shortcut record pattern sugar { f1, f2 } (instead of { f1 = f1, f2 = f2 }

Will be done in a future PR (tracked in #42):

  • allow kernel module features when formatting

Copy link
Copy Markdown
Member

@robinheghan robinheghan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall, this looks good to me. I have a few comments, but nothing major.

Comment thread builder/src/Build.hs Outdated
then
let deps = map Src.getImportName imports
local = Details.Local path time deps (any isMain values) lastChange buildID
local = Details.Local path time deps (any isMain (fmap snd values)) lastChange buildID
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason you're using fmap here instead of the (isMain . snd) approach you use further down?

Comment thread builder/src/Build.hs
Comment thread compiler/src/Canonicalize/Module.hs Outdated
canonicalize pkg ifaces modul@(Src.Module _ exports docs imports values _ _ binops effects) =
canonicalize pkg ifaces modul@(Src.Module _ exports docs imports values' _ _ binops effects) =
do
let values = fmap snd values'
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know the prime char isn't considered bad practice in Haskell, but could we call this ordredValues or something to that effect?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I saw you were using the prime convention elsewhere as well. I struggle a bit with reading those functions, as the name doesn't really give me a clue about what's different from the non-prime version.

Consider using full names where possible. Makes it much easier to read and understand the code.

Comment thread compiler/src/Text/PrettyPrint/Avh4/Block.hs
_aliases :: [A.Located Alias],
_values :: [(SourceOrder, A.Located Value)],
_unions :: [(SourceOrder, A.Located Union)],
_aliases :: [(SourceOrder, A.Located Alias)],
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be better to use a new type A.SourceOrderWithLocation a instead of using (SourceOrder, A.Located a)? That would get rid of having to do fmap snd everywhere, wouldn't it? 🤔

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the fmap snd's you're talking about are fmap'ing a List, and have type [(sourceOrder, A.Located a)] -> A.Located a, so having a A.SourceOrderWithLocation a type would still need an fmap someNewFunction with someNewFunction : A.SourceOrderWithLocation a -> A.Located a, or maybe in some places where A.toValue is used later on, we'd have someOtherNewFunction : A.SourceOrderWithLocation a -> a.

So I think the result would just change fmap snd into fmap someNewFunction, which I think does have different trade-offs, but doesn't seem strictly better to me. But if you think having some more named functions would be worth it to avoid using more tuples, then I can do that. What do you think?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just keep it as is.

A part of me is thinking "there's got to be a way to do this without adding fmaps all over", but I'm sure you're right in your assessment that it would include different trade offs.

The code is easy enough to understand, so just keep it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe the fmap's are a smell that the SourceOrder's are being passed down further than they need to be? I'll keep an eye out for that when I fix the other feedback.

Comment thread compiler/src/Gren/Format.hs Outdated
spaceOrIndent' :: Bool -> NonEmpty Block -> Block
spaceOrIndent' forceMultiline = Block.rowOrIndent' forceMultiline (Just Block.space)

{-# INLINE group #-}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this make a big difference, performance wise?

Copy link
Copy Markdown
Contributor Author

@avh4 avh4 Sep 3, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably not. My estimation is there's maybe a 60% chance this does improve performance at the cost of increased binary size, since this is a large enough function that ghc might not inline it without the hint. But also the improvement is certainly going to be tiny and probably not noticeable.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you mind removing the pragmas, then?

Don't mind keeping it if we know for certain that it makes an important difference in performance, but until we know that to be true I'd rather just trust the compiler to do its thing.

@robinheghan
Copy link
Copy Markdown
Member

Should've mentioned: this is a real impressive piece of work 👏

@avh4 avh4 marked this pull request as ready for review September 7, 2022 07:42
@avh4 avh4 changed the title [WIP] Implement formatting logic Implement formatting logic Sep 7, 2022
@avh4
Copy link
Copy Markdown
Contributor Author

avh4 commented Sep 7, 2022

Comments addressed.

I think this should be working now, and should preserve the meaning of any input code when formatting. Comments will be lost, and linebreak sensitivity isn't implemented in this PR (with a preference toward splitting things onto multiple lines), so it should be enough for people to play around with while #58 gets worked on.

If you have any preferences about the format itself, I'm happy to hear that here, or address it in a later PR.

@avh4 avh4 requested a review from robinheghan September 7, 2022 07:50
@avh4 avh4 changed the title Implement formatting logic Implement formatting logic (without comments support) Sep 7, 2022
@avh4 avh4 changed the title Implement formatting logic (without comments support) Implement formatting logic (without comments/linebreak support) Sep 7, 2022
@robinheghan
Copy link
Copy Markdown
Member

Just tested out this branch by formatting gren-lang/core and gren-lang/browser. There are some files that fail to parse, but it seems parsing works correctly when running gren make. Any idea why those files fail?

The code in this PR looks good to me, just want to know why certain files fail before an eventual merge.

@avh4
Copy link
Copy Markdown
Contributor Author

avh4 commented Sep 9, 2022

There are some files that fail to parse, but it seems parsing works correctly when running gren make. Any idea why those files fail?

Yeah, that's because the parser allows different things depending on if the project is a package and if it's a special package that can have kernel features. Currently when I parse for formatting, I just tell it it's an application project. I'll make that work better in a future PR. For now, that means that modules that have infix operator definitions, and effect manager modules won't be able to parse.

@robinheghan
Copy link
Copy Markdown
Member

Thank you for explaining it.

LGTM.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants