Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Futhark Formatter #2178

Draft
wants to merge 52 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
c937138
futhark fmt: quick and dirty draft.
athas May 9, 2023
c9c530c
Merge branch 'master' into fmt
athas Aug 28, 2024
7f0335b
Defined a monad to encapsulate comments
thereseLyngby Sep 2, 2024
2e74400
Encapsulated comments in a monad
thereseLyngby Sep 3, 2024
0e50e3e
working on formatting single- and multi-line
WilliamDue Sep 4, 2024
af0b7f1
Done
WilliamDue Sep 9, 2024
ce24d08
Split up the project into more files.
WilliamDue Sep 9, 2024
dcdce9e
Revert "Split up the project into more files."
WilliamDue Sep 10, 2024
8a0a50f
We have attributes, we have an idea for better comment printing, we a…
WilliamDue Sep 10, 2024
e0c3392
Trailing comments, heck yes.
WilliamDue Sep 10, 2024
8be03a0
We have more cases of the syntax tree we have formatted.
WilliamDue Sep 11, 2024
0bf7c82
Implemented more fmtTypeExp cases
thereseLyngby Sep 13, 2024
61ef9ae
Co-authored-by: thereseLyngby <[email protected]>
WilliamDue Sep 16, 2024
89fb43e
Did not add that many cases.
WilliamDue Sep 16, 2024
1f2db4a
Added further cases for formatting declarations
thereseLyngby Sep 17, 2024
524760b
More cases, I think expressions are done now.
WilliamDue Sep 17, 2024
10d5c67
Added some tests and we have made all cases .
WilliamDue Sep 18, 2024
6068c33
More tests passes.
WilliamDue Sep 21, 2024
b93b400
Our tests passes it seems.
WilliamDue Sep 22, 2024
1aa3949
Started writing idempotence test for fmt. Problem: how to compare AST?
thereseLyngby Sep 23, 2024
f2882a2
I forgot to commit this.
WilliamDue Sep 23, 2024
6299c5d
Formatting.
WilliamDue Sep 23, 2024
6cffc29
Removed these warnings.
WilliamDue Sep 23, 2024
772e36d
Hopefully everything is correct now.
WilliamDue Sep 23, 2024
bc2c081
Added tests for comparison of token stream before and after fmt
thereseLyngby Sep 30, 2024
d6f1fff
Updating the Fmt datatype
thereseLyngby Oct 1, 2024
958af54
Finished setup of fmt-type and renamed Fmt.hs to Printer.hs
thereseLyngby Oct 1, 2024
62b038c
Fix some file structure.
WilliamDue Oct 1, 2024
ace4b5c
This breaks alot of formatting of code but I am very eepy so this is …
WilliamDue Oct 1, 2024
9e23f19
More lines.
WilliamDue Oct 2, 2024
c3b61c0
Possibly infinite loop.
WilliamDue Oct 2, 2024
5443acc
I found my error.
WilliamDue Oct 2, 2024
0a72ef7
These should not be here.
WilliamDue Oct 2, 2024
fcf325a
Too tired to continue by now but I am getting somewhere.
WilliamDue Oct 2, 2024
ee14706
futhark fmt: allow input on stdin as well.
athas Oct 3, 2024
c5ce4d2
Gonna go to bed.
WilliamDue Oct 4, 2024
69ed6d3
Minor changes.
WilliamDue Oct 5, 2024
7876345
More multiline formats.
WilliamDue Oct 7, 2024
4b8ad0c
Started on writing some tests in bash.
WilliamDue Oct 7, 2024
7200c5f
Passing more tests.
WilliamDue Oct 7, 2024
0cfb830
Bed time.
WilliamDue Oct 7, 2024
8d85a74
New tests.
WilliamDue Oct 8, 2024
6e0605c
These tests were not the original ones.
WilliamDue Oct 8, 2024
4abbfc6
Type check and a correct test.
WilliamDue Oct 9, 2024
9a3681b
fixed a few bugs
thereseLyngby Oct 9, 2024
2efcc7e
More tests passes and the test script is better.
WilliamDue Oct 13, 2024
a1fb6b4
Remove result files.
WilliamDue Oct 14, 2024
b8d3cb0
Most tests passes by now.
WilliamDue Oct 14, 2024
8f75446
Switched to using Prettyprinter for now.
WilliamDue Oct 14, 2024
e981fc8
A start on a better design.
WilliamDue Oct 14, 2024
696bd07
Cleaner code.
WilliamDue Oct 14, 2024
845fe66
I have broken a few things when trying to make better formatting.
WilliamDue Oct 14, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -629,6 +629,9 @@ jobs:
- run: |
cd tests_adhoc && sh test.sh

- run: |
sh tests_fmt/test.sh tests

- run: |
futhark doc -o prelude-docs /dev/null
tar -Jcf prelude-docs.tar.xz prelude-docs
Expand Down
3 changes: 2 additions & 1 deletion Setup.hs
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ import Distribution.Simple

main :: IO ()
main = defaultMainWithHooks myHooks
where myHooks = simpleUserHooks
where
myHooks = simpleUserHooks
3 changes: 3 additions & 0 deletions futhark.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,9 @@ library
Futhark.CLI.Dev
Futhark.CLI.Doc
Futhark.CLI.Eval
Futhark.CLI.Fmt
Futhark.CLI.Fmt.Printer
Futhark.CLI.Fmt.AST
Futhark.CLI.HIP
Futhark.CLI.Literate
Futhark.CLI.LSP
Expand Down
25 changes: 25 additions & 0 deletions src/Futhark/CLI/Fmt.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
-- | @futhark fmt@
module Futhark.CLI.Fmt (main) where

import Data.Text.IO qualified as T
import Futhark.CLI.Fmt.Printer
import Futhark.Util.Options
import Language.Futhark
import Language.Futhark.Parser (SyntaxError (..))
import System.Exit
import System.IO

-- | Run @futhark fmt@.
main :: String -> [String] -> IO ()
main = mainWithOptions () [] "program" $ \args () ->
case args of
[] -> Just $ onInput =<< T.getContents
[file] -> Just $ onInput =<< T.readFile file
_any -> Nothing
where
onInput s = do
case fmtText "<stdin>" s of
Left (SyntaxError loc err) -> do
T.hPutStr stderr $ locText loc <> ":\n" <> prettyText err
exitFailure
Right fmt -> T.hPutStr stdout fmt
238 changes: 238 additions & 0 deletions src/Futhark/CLI/Fmt/AST.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
module Futhark.CLI.Fmt.AST
( Fmt,
-- functions for building fmt
nil,
nest,
stdNest,
code,
space,
line,
comment,
sep,
sepSpace,
sepLine,
brackets,
braces,
parens,
(<+>),
(</>),
(<:>),
(<+/>),
indent,
stdIndent,
colon,
sepNonEmpty,
pretty,
isEmpty,
FmtM,
fmtComments,
buildFmt,
popComments,
runFormat,
Format (..),
align,
sepLoc
)
where

import Data.Text qualified as T
import Prettyprinter qualified as P hiding (Doc)
import Prettyprinter (Doc)
import Prettyprinter.Render.Text (renderStrict)
import Control.Monad.Identity ( Identity(..) )
import Control.Monad.Reader
( ReaderT(..), MonadReader(..) )
import Control.Monad.State
( StateT, gets, modify, MonadState(..), evalStateT )
import Data.Loc ( Located(..), Loc(..), posLine)
import Language.Futhark.Parser.Monad ( Comment(..) )

-- These are left associative since we want to evaluate the monadic
-- computation from left to right. Since the left most expression is
-- printed first and our monad is checking if a comment should be
-- printed.
infixl 6 <:>
infixl 6 <+>
infixl 6 </>
infixl 7 <+/>

newtype Fmt = Fmt (Doc ())

newtype FmtState = FmtState
{comments :: [Comment]}
deriving (Show, Eq, Ord)

data Layout = MultiLine | SingleLine deriving (Show, Eq)

-- State monad to keep track of comments and layout.
type FmtM a = ReaderT Layout (StateT FmtState Identity) a

class Format a where
fmt :: a -> FmtM Fmt

instance Format (FmtM Fmt) where
fmt = id

instance Format T.Text where
fmt = code

instance Format Comment where
fmt = comment . commentText

-- Functions for operating on FmtM monad
fmtComments :: (Located a) => a -> FmtM Fmt
fmtComments a = do
s <- get
case comments s of
c : cs | locOf a > locOf c -> do
put $ s {comments = cs}
fmt c <:> fmtComments a -- fmts remaining comments
_ -> nil

-- parses comments infront of a and converts a to fmt using formatting function f
buildFmt :: (Located a) => a -> FmtM Fmt -> FmtM Fmt -> FmtM Fmt
buildFmt a single multi = local (const $ lineLayout a) $ do
Fmt c <- fmtComments a
m <- ask
Fmt a' <- if m == SingleLine then single else multi
-- c' <- trailingComment a
pure $ toFmt $ c <> a'

lineLayout :: (Located a) => a -> Layout
lineLayout a =
case locOf a of
Loc start end ->
if posLine start == posLine end
then SingleLine
else MultiLine
NoLoc -> undefined -- should throw an error

popComments :: FmtM Fmt
popComments = do
cs <- gets comments
modify (\s -> s {comments = []})
sep nil cs

(<+/>) :: (Format a, Format b, Located b) => a -> b -> FmtM Fmt
(<+/>) a b =
case lineLayout b of
MultiLine -> a </> stdIndent b
SingleLine -> a <+> b

runFormat :: FmtM a -> [Comment] -> a
runFormat format cs = runIdentity $ evalStateT (runReaderT format e) s
where
s = FmtState {comments = cs}
e = MultiLine

nil :: FmtM Fmt
nil = pure $ toFmt mempty

mapFmt :: (Doc () -> Doc ()) -> Fmt -> Fmt
mapFmt f (Fmt a) = Fmt $ f a

mapFmtM :: (Doc () -> Doc ()) -> FmtM Fmt -> FmtM Fmt
mapFmtM f = fmap (mapFmt f)

unFmt :: Fmt -> Doc ()
unFmt (Fmt a) = a

unFmtM :: FmtM Fmt -> FmtM (Doc ())
unFmtM = fmap unFmt

toFmt :: Doc () -> Fmt
toFmt = Fmt

toFmtM :: FmtM (Doc ()) -> FmtM Fmt
toFmtM = fmap toFmt

nest :: (Format a) => Int -> a -> FmtM Fmt
nest i = mapFmtM (P.nest i) . fmt

space :: FmtM Fmt
space = pure $ Fmt P.space

line :: FmtM Fmt
line = pure $ Fmt P.line

sep :: (Format a, Format b) => a -> [b] -> FmtM Fmt
sep s xs = toFmtM $ aux <$> unFmtM (fmt s) <*> mapM (unFmtM . fmt) xs
where
aux z = P.concatWith (\a b -> a <> z <> b)

-- I am not sure this fulfills idemppotence.
sepLoc :: (Format a, Located a) => [a] -> FmtM Fmt
sepLoc [] = nil
sepLoc (y:ys) = fmt y <:> auxiliary Nothing nil ys
where
auxiliary :: (Format a, Located a) => Maybe a -> FmtM Fmt -> [a] -> FmtM Fmt
auxiliary _ acc [] = acc
auxiliary prev acc (x:xs) =
if lineLayout x == SingleLine && isSame
then auxiliary (Just x) (acc <+> x) xs
else auxiliary (Just x) (acc </> x) xs
where

isSame =
case (locOf <$> prev, locOf x) of
(Just (Loc s0 e0), Loc s1 e1) ->
posLine s0 == posLine s1 &&
posLine e0 == posLine e1
_any -> True

stdNest :: (Format a) => a -> FmtM Fmt
stdNest = nest 2

align :: (Format a) => a -> FmtM Fmt
align = mapFmtM P.align . fmt

indent :: (Format a) => Int -> a -> FmtM Fmt
indent i = mapFmtM (P.indent i) . fmt

stdIndent :: Format a => a -> FmtM Fmt
stdIndent = indent 2

code :: T.Text -> FmtM Fmt
code = pure . toFmt . P.pretty

comment :: T.Text -> FmtM Fmt
comment = (<:> line) . code

brackets :: (Format a) => a -> FmtM Fmt
brackets = mapFmtM P.brackets . fmt

braces :: (Format a) => a -> FmtM Fmt
braces = mapFmtM P.braces . fmt

parens :: (Format a) => a -> FmtM Fmt
parens = mapFmtM P.parens . fmt

sepSpace :: (Format a, Format b) => a -> [b] -> FmtM Fmt
sepSpace s = sep (fmt s <:> space)

sepLine :: (Format a, Format b) => a -> [b] -> FmtM Fmt
sepLine s = sep (line <:> fmt s)

(<:>) :: (Format a, Format b) => a -> b -> FmtM Fmt
a <:> b = toFmtM $ (P.<>) <$> unFmtM (fmt a) <*> unFmtM (fmt b)

(<+>) :: (Format a, Format b) => a -> b -> FmtM Fmt
a <+> b = a <:> space <:> b

(</>) :: (Format a, Format b) => a -> b -> FmtM Fmt
a </> b = a <:> line <:> b

colon :: FmtM Fmt
colon = pure $ toFmt P.colon

sepNonEmpty :: (Format a, Format b) => a -> [b] -> FmtM Fmt
sepNonEmpty = sep

layoutOpts :: P.LayoutOptions
layoutOpts = P.LayoutOptions P.Unbounded

pretty :: Fmt -> T.Text
pretty = renderStrict . P.layoutPretty layoutOpts . unFmt

isEmpty :: Fmt -> Bool
isEmpty = const False
Loading
Loading