brittany/source/library/Language/Haskell/Brittany/Internal/StepOrchestrate.hs

257 lines
11 KiB
Haskell

{-# LANGUAGE NoImplicitPrelude #-}
module Language.Haskell.Brittany.Internal.StepOrchestrate
( processModule
)
where
import Language.Haskell.Brittany.Internal.Prelude
import qualified Control.Monad.Trans.MultiRWS.Strict
as MultiRWSS
import Data.CZipWith
import qualified Data.Map.Strict as Map
-- import qualified Data.Sequence as Seq
import qualified Data.Text.Lazy as TextL
import qualified Data.Text.Lazy.Builder as TextL.Builder
import qualified Data.Text.Lazy.Builder as Text.Builder
import qualified GHC
import GHC ( EpaCommentTok
( EpaBlockComment
, EpaEofComment
, EpaLineComment
)
, GenLocated(L)
, HsModule(HsModule)
, LHsDecl
, SrcSpanAnn'(SrcSpanAnn)
)
import qualified GHC.OldList as List
import GHC.Types.SrcLoc ( srcSpanFileName_maybe )
import qualified Language.Haskell.GHC.ExactPrint
as ExactPrint
import Language.Haskell.Brittany.Internal.Components.BriDoc
import Language.Haskell.Brittany.Internal.Config.Types
import Language.Haskell.Brittany.Internal.Config.Types.Instances2
( )
import Language.Haskell.Brittany.Internal.S2_SplitModule
( splitModule )
import Language.Haskell.Brittany.Internal.S3_ToBriDocTools
import Language.Haskell.Brittany.Internal.S4_WriteBriDoc
( ppBriDoc )
import Language.Haskell.Brittany.Internal.ToBriDoc.Decl
import Language.Haskell.Brittany.Internal.ToBriDoc.Import
import Language.Haskell.Brittany.Internal.ToBriDoc.Module
import Language.Haskell.Brittany.Internal.Types
import Language.Haskell.Brittany.Internal.Util.AST
import Language.Haskell.Brittany.Internal.Utils
-- BrittanyErrors can be non-fatal warnings, thus both are returned instead
-- of an Either.
-- This should be cleaned up once it is clear what kinds of errors really
-- can occur.
processModule
:: TraceFunc
-> Config
-> PerItemConfig
-> GHC.ParsedSource
-> IO ([BrittanyError], TextL.Text)
processModule traceFunc conf inlineConf parsedModule = do
let shouldReformatHead =
conf & _conf_layout & _lconfig_reformatModulePreamble & confUnpack @Bool
let
wrapNonDeclToBriDoc = MultiRWSS.withMultiReader conf
. MultiRWSS.withMultiState_ (CommentCounter 0)
FinalList moduleElementsStream = splitModule
shouldReformatHead
parsedModule
(obtainAnnPos (GHC.hsmodAnn $ GHC.unLoc parsedModule) GHC.AnnWhere)
((out, errs), debugStrings) =
runIdentity
$ MultiRWSS.runMultiRWSTNil
$ MultiRWSS.withMultiWriterAW
$ MultiRWSS.withMultiWriterAW
$ MultiRWSS.withMultiWriterW
$ MultiRWSS.withMultiReader traceFunc
$ moduleElementsStream
(\modElem cont -> do
case modElem of
MEExactModuleHead modHead -> wrapNonDeclToBriDoc $ do
bdMay <- ppModuleHead modHead
case bdMay of
Nothing -> pure ()
Just bd -> ppBriDoc bd
MEPrettyModuleHead (L loc modHead) -> wrapNonDeclToBriDoc $ do
case modHead of
HsModule epAnn _layoutInfo Nothing Nothing _ _ _ _ -> do
(bd, _) <-
briDocMToPPM
$ maybe id
docFlushRemaining
(srcSpanFileName_maybe loc)
$ docHandleComms epAnn docSeparator
ppBriDoc bd
HsModule _ _layoutInfo Nothing Just{} _ _ _ _ -> error
"brittany internal error: exports without module name"
HsModule epAnn _layoutInfo (Just n) les _ _ _ _ -> do
let startDelta = obtainAnnDeltaPos epAnn GHC.AnnModule
tellDebugMess (show startDelta)
case startDelta of
Nothing -> pure ()
Just GHC.SameLine{} -> pure ()
Just (GHC.DifferentLine r _) ->
mTell $ TextL.Builder.fromString $ replicate (r - 1) '\n'
(bd, _) <-
briDocMToPPM
$ maybe id
docFlushRemaining
(srcSpanFileName_maybe loc)
$ moduleNameExportBridoc epAnn n les
ppBriDoc bd
MEImportDecl importDecl immediateAfterComms ->
wrapNonDeclToBriDoc $ do
(bd, _) <-
briDocMToPPM
$ docSeq
( layoutImport importDecl
: map commentToDoc immediateAfterComms
)
ppBriDoc bd
MEDecl decl immediateAfterComms -> do
let declConfig = getDeclConfig conf inlineConf decl
MultiRWSS.withMultiReader declConfig
$ MultiRWSS.withMultiState_ (CommentCounter 0)
$ ppToplevelDecl decl immediateAfterComms
MEComment (ind, EpaLineComment str) -> do
mTell $ TextL.Builder.fromString (replicate ind ' ' ++ str)
mTell $ TextL.Builder.fromString "\n"
MEComment (ind, EpaBlockComment str) -> do
mTell $ TextL.Builder.fromString (replicate ind ' ' ++ str)
mTell $ TextL.Builder.fromString "\n"
MEComment (_, EpaEofComment) -> pure ()
MEComment _ ->
mTell $ TextL.Builder.fromString "some other comment"
MEWhitespace dp -> do
-- mTell $ TextL.Builder.fromString "B"
-- mTell $ TextL.Builder.fromString ("whitespace " ++ show dp)
ppmMoveToExactLoc dp
cont
)
(\x -> do
-- mTell $ TextL.Builder.fromString "\n"
pure x
)
-- _tracer =
-- -- if Seq.null debugStrings
-- -- then id
-- -- else
-- trace ("---- DEBUGMESSAGES ---- ")
-- . foldr (seq . join trace) id debugStrings
debugStrings `forM_` \s -> useTraceFunc traceFunc s
moduleElementsStream
(\el rest -> do
case el of
MEExactModuleHead{} -> useTraceFunc traceFunc "MEExactModuleHead"
MEPrettyModuleHead{} -> useTraceFunc traceFunc "MEPrettyModuleHead"
MEImportDecl{} -> useTraceFunc traceFunc "MEImportDecl"
MEDecl{} -> useTraceFunc traceFunc "MEDecl"
MEComment{} -> useTraceFunc traceFunc "MEComment"
MEWhitespace dp -> useTraceFunc traceFunc ("MEWhitespace " ++ show dp)
rest
)
(\_ -> pure ())
pure (errs, TextL.Builder.toLazyText out)
commentToDoc :: (Int, EpaCommentTok) -> ToBriDocM BriDocNumbered
commentToDoc (indent, c) = case c of
GHC.EpaDocCommentNext str -> docLitS (replicate indent ' ' ++ str)
GHC.EpaDocCommentPrev str -> docLitS (replicate indent ' ' ++ str)
GHC.EpaDocCommentNamed str -> docLitS (replicate indent ' ' ++ str)
GHC.EpaDocSection _ str -> docLitS (replicate indent ' ' ++ str)
GHC.EpaDocOptions str -> docLitS (replicate indent ' ' ++ str)
GHC.EpaLineComment str -> docLitS (replicate indent ' ' ++ str)
GHC.EpaBlockComment str -> docLitS (replicate indent ' ' ++ str)
GHC.EpaEofComment -> docEmpty
-- Prints the information associated with the module annotation
-- This includes the imports
-- This returns a `Maybe` because it only produces a BriDocNumbered if
-- re-formatting the module head is enabled. We maybe should change that
-- for consistency.
ppModuleHead :: GHC.ParsedSource -> PPMLocal (Maybe BriDocNumbered)
ppModuleHead lmod = do
processDefault lmod $> Nothing
processDefault
:: (ExactPrint.ExactPrint ast, MonadMultiWriter Text.Builder.Builder m)
-- , MonadMultiReader ExactPrint.Types.Anns m
=> GHC.Located ast
-> m ()
processDefault x = do
let str = ExactPrint.exactPrint x
-- this hack is here so our print-empty-module trick does not add
-- a newline at the start if there actually is no module header / imports
-- / anything.
-- TODO: instead the appropriate annotation could be removed when "cleaning"
-- the module (header). This would remove the need for this hack!
case str of
"\n" -> return ()
_ -> mTell $ Text.Builder.fromString $ List.dropWhileEnd ((==) '\n') str
getDeclConfig
:: Config
-> PerItemConfig
-> GHC.LHsDecl GhcPs
-> Config
getDeclConfig config inlineConf decl = cZipWith fromOptionIdentity config
$ mconcat (catMaybes (mBindingConfs ++ [mDeclConf]))
where
declBindingNames = getDeclBindingNames decl
mBindingConfs =
declBindingNames <&> \n -> Map.lookup n $ _icd_perBinding inlineConf
mDeclConf = case GHC.locA $ GHC.getLoc decl of
GHC.RealSrcSpan x _ -> Map.lookup x $ _icd_perAnchor inlineConf
GHC.UnhelpfulSpan{} -> Nothing
ppToplevelDecl :: LHsDecl GhcPs -> [(Int, EpaCommentTok)] -> PPMLocal ()
ppToplevelDecl decl immediateAfterComms = do
exactprintOnly <- mAsk <&> \declConfig ->
declConfig & _conf_roundtrip_exactprint_only & confUnpack @Bool
bd <- fmap fst $ if exactprintOnly
then briDocMToPPM
$ docSeq (briDocByExact decl : map commentToDoc immediateAfterComms)
else do
let innerDoc = case decl of
L (SrcSpanAnn _ (GHC.RealSrcSpan s _)) _ ->
docFlushRemaining (GHC.srcSpanFile s) $ layoutDecl decl
_ -> layoutDecl decl
(r, errorCount) <- briDocMToPPM
$ docSeq (innerDoc : map commentToDoc immediateAfterComms)
if errorCount == 0 then pure (r, 0) else briDocMToPPM $ briDocByExact decl
ppBriDoc bd
let commCntIn = connectedCommentCount decl
commCntOut <- mGet
when (commCntIn /= commCntOut) $ if commCntOut < commCntIn
then mTell
[ ErrorUnusedComments decl
(unCommentCounter commCntIn)
(unCommentCounter commCntOut)
]
else mTell
[ ErrorUnusedComments decl
(unCommentCounter commCntIn)
(unCommentCounter commCntOut)
]
-- error
-- $ "internal brittany error: inconsistent comment count ("
-- ++ show commCntOut
-- ++ ">"
-- ++ show commCntIn
-- ++ ")!"