661 lines
25 KiB
Haskell
661 lines
25 KiB
Haskell
{-# LANGUAGE LambdaCase #-}
|
|
{-# LANGUAGE NoImplicitPrelude #-}
|
|
{-# LANGUAGE ScopedTypeVariables #-}
|
|
|
|
module Language.Haskell.Brittany.Internal
|
|
( parsePrintModule
|
|
, parsePrintModuleTests
|
|
, pPrintModule
|
|
, pPrintModuleAndCheck
|
|
-- re-export from utils:
|
|
, parseModule
|
|
, parseModuleFromString
|
|
, extractCommentConfigs
|
|
, getTopLevelDeclNameMap
|
|
) where
|
|
|
|
import Control.Monad.Trans.Except
|
|
import qualified Control.Monad.Trans.MultiRWS.Strict as MultiRWSS
|
|
import qualified Data.ByteString.Char8
|
|
import Data.CZipWith
|
|
import Data.Char (isSpace)
|
|
import Data.HList.HList
|
|
import qualified Data.Map as Map
|
|
import qualified Data.Maybe
|
|
import qualified Data.Semigroup as Semigroup
|
|
import qualified Data.Sequence as Seq
|
|
import qualified Data.Text as Text
|
|
import qualified Data.Text.Lazy as TextL
|
|
import qualified Data.Text.Lazy.Builder as Text.Builder
|
|
import qualified Data.Yaml
|
|
import qualified GHC hiding (parseModule)
|
|
import GHC (GenLocated(L))
|
|
import qualified GHC.Driver.Session as GHC
|
|
import GHC.Hs
|
|
import qualified GHC.LanguageExtensions.Type as GHC
|
|
import qualified GHC.OldList as List
|
|
import GHC.Parser.Annotation (AnnKeywordId(..))
|
|
import GHC.Types.SrcLoc (SrcSpan)
|
|
import Language.Haskell.Brittany.Internal.Backend
|
|
import Language.Haskell.Brittany.Internal.BackendUtils
|
|
import Language.Haskell.Brittany.Internal.Config
|
|
import Language.Haskell.Brittany.Internal.Config.Types
|
|
import Language.Haskell.Brittany.Internal.ExactPrintUtils
|
|
import Language.Haskell.Brittany.Internal.LayouterBasics
|
|
import Language.Haskell.Brittany.Internal.Layouters.Decl
|
|
import Language.Haskell.Brittany.Internal.Layouters.Module
|
|
import Language.Haskell.Brittany.Internal.Prelude
|
|
import Language.Haskell.Brittany.Internal.PreludeUtils
|
|
import Language.Haskell.Brittany.Internal.Transformations.Alt
|
|
import Language.Haskell.Brittany.Internal.Transformations.Columns
|
|
import Language.Haskell.Brittany.Internal.Transformations.Floating
|
|
import Language.Haskell.Brittany.Internal.Transformations.Indent
|
|
import Language.Haskell.Brittany.Internal.Transformations.Par
|
|
import Language.Haskell.Brittany.Internal.Types
|
|
import Language.Haskell.Brittany.Internal.Utils
|
|
import qualified Language.Haskell.GHC.ExactPrint as ExactPrint
|
|
import qualified Language.Haskell.GHC.ExactPrint.Types as ExactPrint
|
|
import qualified UI.Butcher.Monadic as Butcher
|
|
|
|
|
|
|
|
data InlineConfigTarget
|
|
= InlineConfigTargetModule
|
|
| InlineConfigTargetNextDecl -- really only next in module
|
|
| InlineConfigTargetNextBinding -- by name
|
|
| InlineConfigTargetBinding String
|
|
|
|
extractCommentConfigs
|
|
:: ExactPrint.Anns
|
|
-> TopLevelDeclNameMap
|
|
-> Either (String, String) (CConfig Maybe, PerItemConfig)
|
|
extractCommentConfigs anns (TopLevelDeclNameMap declNameMap) = do
|
|
let
|
|
commentLiness =
|
|
[ ( k
|
|
, [ x
|
|
| (ExactPrint.Comment x _ _, _) <-
|
|
( ExactPrint.annPriorComments ann
|
|
++ ExactPrint.annFollowingComments ann
|
|
)
|
|
]
|
|
++ [ x
|
|
| (ExactPrint.AnnComment (ExactPrint.Comment x _ _), _) <-
|
|
ExactPrint.annsDP ann
|
|
]
|
|
)
|
|
| (k, ann) <- Map.toList anns
|
|
]
|
|
let configLiness = commentLiness <&> second
|
|
(Data.Maybe.mapMaybe $ \line -> do
|
|
l1 <-
|
|
List.stripPrefix "-- BRITTANY" line
|
|
<|> List.stripPrefix "--BRITTANY" line
|
|
<|> List.stripPrefix "-- brittany" line
|
|
<|> List.stripPrefix "--brittany" line
|
|
<|> (List.stripPrefix "{- BRITTANY" line >>= stripSuffix "-}")
|
|
let l2 = dropWhile isSpace l1
|
|
guard
|
|
( ("@" `isPrefixOf` l2)
|
|
|| ("-disable" `isPrefixOf` l2)
|
|
|| ("-next" `isPrefixOf` l2)
|
|
|| ("{" `isPrefixOf` l2)
|
|
|| ("--" `isPrefixOf` l2)
|
|
)
|
|
pure l2
|
|
)
|
|
let
|
|
configParser = Butcher.addAlternatives
|
|
[ ( "commandline-config"
|
|
, \s -> "-" `isPrefixOf` dropWhile (== ' ') s
|
|
, cmdlineConfigParser
|
|
)
|
|
, ( "yaml-config-document"
|
|
, \s -> "{" `isPrefixOf` dropWhile (== ' ') s
|
|
, Butcher.addCmdPart (Butcher.varPartDesc "yaml-config-document")
|
|
$ fmap (\lconf -> (mempty { _conf_layout = lconf }, ""))
|
|
. either (const Nothing) Just
|
|
. Data.Yaml.decodeEither'
|
|
. Data.ByteString.Char8.pack
|
|
-- TODO: use some proper utf8 encoder instead?
|
|
)
|
|
]
|
|
parser = do -- we will (mis?)use butcher here to parse the inline config
|
|
-- line.
|
|
let nextDecl = do
|
|
conf <- configParser
|
|
Butcher.addCmdImpl (InlineConfigTargetNextDecl, conf)
|
|
Butcher.addCmd "-next-declaration" nextDecl
|
|
Butcher.addCmd "-Next-Declaration" nextDecl
|
|
Butcher.addCmd "-NEXT-DECLARATION" nextDecl
|
|
let nextBinding = do
|
|
conf <- configParser
|
|
Butcher.addCmdImpl (InlineConfigTargetNextBinding, conf)
|
|
Butcher.addCmd "-next-binding" nextBinding
|
|
Butcher.addCmd "-Next-Binding" nextBinding
|
|
Butcher.addCmd "-NEXT-BINDING" nextBinding
|
|
let disableNextBinding = do
|
|
Butcher.addCmdImpl
|
|
( InlineConfigTargetNextBinding
|
|
, mempty { _conf_roundtrip_exactprint_only = pure $ pure True }
|
|
)
|
|
Butcher.addCmd "-disable-next-binding" disableNextBinding
|
|
Butcher.addCmd "-Disable-Next-Binding" disableNextBinding
|
|
Butcher.addCmd "-DISABLE-NEXT-BINDING" disableNextBinding
|
|
let disableNextDecl = do
|
|
Butcher.addCmdImpl
|
|
( InlineConfigTargetNextDecl
|
|
, mempty { _conf_roundtrip_exactprint_only = pure $ pure True }
|
|
)
|
|
Butcher.addCmd "-disable-next-declaration" disableNextDecl
|
|
Butcher.addCmd "-Disable-Next-Declaration" disableNextDecl
|
|
Butcher.addCmd "-DISABLE-NEXT-DECLARATION" disableNextDecl
|
|
let disableFormatting = do
|
|
Butcher.addCmdImpl
|
|
( InlineConfigTargetModule
|
|
, mempty { _conf_disable_formatting = pure $ pure True }
|
|
)
|
|
Butcher.addCmd "-disable" disableFormatting
|
|
Butcher.addCmd "@" $ do
|
|
-- Butcher.addCmd "module" $ do
|
|
-- conf <- configParser
|
|
-- Butcher.addCmdImpl (InlineConfigTargetModule, conf)
|
|
Butcher.addNullCmd $ do
|
|
bindingName <- Butcher.addParamString "BINDING" mempty
|
|
conf <- configParser
|
|
Butcher.addCmdImpl (InlineConfigTargetBinding bindingName, conf)
|
|
conf <- configParser
|
|
Butcher.addCmdImpl (InlineConfigTargetModule, conf)
|
|
lineConfigss <- configLiness `forM` \(k, ss) -> do
|
|
r <- ss `forM` \s -> case Butcher.runCmdParserSimple s parser of
|
|
Left err -> Left $ (err, s)
|
|
Right c -> Right $ c
|
|
pure (k, r)
|
|
|
|
let perModule = foldl'
|
|
(<>)
|
|
mempty
|
|
[ conf
|
|
| (_ , lineConfigs) <- lineConfigss
|
|
, (InlineConfigTargetModule, conf ) <- lineConfigs
|
|
]
|
|
let
|
|
perBinding = Map.fromListWith
|
|
(<>)
|
|
[ (n, conf)
|
|
| (k , lineConfigs) <- lineConfigss
|
|
, (target, conf ) <- lineConfigs
|
|
, n <- case target of
|
|
InlineConfigTargetBinding s -> [s]
|
|
InlineConfigTargetNextBinding | Just name <- Map.lookup k declNameMap ->
|
|
[name]
|
|
_ -> []
|
|
]
|
|
let
|
|
perKey = Map.fromListWith
|
|
(<>)
|
|
[ (k, conf)
|
|
| (k , lineConfigs) <- lineConfigss
|
|
, (target, conf ) <- lineConfigs
|
|
, case target of
|
|
InlineConfigTargetNextDecl -> True
|
|
InlineConfigTargetNextBinding | Nothing <- Map.lookup k declNameMap ->
|
|
True
|
|
_ -> False
|
|
]
|
|
|
|
pure
|
|
$ ( perModule
|
|
, PerItemConfig { _icd_perBinding = perBinding, _icd_perKey = perKey }
|
|
)
|
|
|
|
|
|
getTopLevelDeclNameMap :: GHC.ParsedSource -> TopLevelDeclNameMap
|
|
getTopLevelDeclNameMap (L _ (HsModule _ _name _exports _ decls _ _)) =
|
|
TopLevelDeclNameMap $ Map.fromList
|
|
[ (ExactPrint.mkAnnKey decl, name)
|
|
| decl <- decls
|
|
, (name : _) <- [getDeclBindingNames decl]
|
|
]
|
|
|
|
|
|
-- | Exposes the transformation in an pseudo-pure fashion. The signature
|
|
-- contains `IO` due to the GHC API not exposing a pure parsing function, but
|
|
-- there should be no observable effects.
|
|
--
|
|
-- Note that this function ignores/resets all config values regarding
|
|
-- debugging, i.e. it will never use `trace`/write to stderr.
|
|
--
|
|
-- Note that the ghc parsing function used internally currently is wrapped in
|
|
-- `mask_`, so cannot be killed easily. If you don't control the input, you
|
|
-- may wish to put some proper upper bound on the input's size as a timeout
|
|
-- won't do.
|
|
parsePrintModule :: Config -> Text -> IO (Either [BrittanyError] Text)
|
|
parsePrintModule configWithDebugs inputText = runExceptT $ do
|
|
let config =
|
|
configWithDebugs { _conf_debug = _conf_debug staticDefaultConfig }
|
|
let ghcOptions = config & _conf_forward & _options_ghc & runIdentity
|
|
let config_pp = config & _conf_preprocessor
|
|
let cppMode = config_pp & _ppconf_CPPMode & confUnpack
|
|
let hackAroundIncludes = config_pp & _ppconf_hackAroundIncludes & confUnpack
|
|
(anns, parsedSource, hasCPP) <- do
|
|
let hackF s = if "#include" `isPrefixOf` s
|
|
then "-- BRITANY_INCLUDE_HACK " ++ s
|
|
else s
|
|
let hackTransform = if hackAroundIncludes
|
|
then List.intercalate "\n" . fmap hackF . lines'
|
|
else id
|
|
let cppCheckFunc dynFlags = if GHC.xopt GHC.Cpp dynFlags
|
|
then case cppMode of
|
|
CPPModeAbort -> return $ Left "Encountered -XCPP. Aborting."
|
|
CPPModeWarn -> return $ Right True
|
|
CPPModeNowarn -> return $ Right True
|
|
else return $ Right False
|
|
parseResult <- lift $ parseModuleFromString
|
|
ghcOptions
|
|
"stdin"
|
|
cppCheckFunc
|
|
(hackTransform $ Text.unpack inputText)
|
|
case parseResult of
|
|
Left err -> throwE [ErrorInput err]
|
|
Right x -> pure x
|
|
(inlineConf, perItemConf) <-
|
|
either (throwE . (: []) . uncurry ErrorMacroConfig) pure
|
|
$ extractCommentConfigs anns (getTopLevelDeclNameMap parsedSource)
|
|
let moduleConfig = cZipWith fromOptionIdentity config inlineConf
|
|
let disableFormatting = moduleConfig & _conf_disable_formatting & confUnpack
|
|
if disableFormatting
|
|
then do
|
|
return inputText
|
|
else do
|
|
(errsWarns, outputTextL) <- do
|
|
let omitCheck =
|
|
moduleConfig
|
|
& _conf_errorHandling
|
|
& _econf_omit_output_valid_check
|
|
& confUnpack
|
|
(ews, outRaw) <- if hasCPP || omitCheck
|
|
then return $ pPrintModule moduleConfig perItemConf anns parsedSource
|
|
else lift
|
|
$ pPrintModuleAndCheck moduleConfig perItemConf anns parsedSource
|
|
let hackF s = fromMaybe s
|
|
$ TextL.stripPrefix (TextL.pack "-- BRITANY_INCLUDE_HACK ") s
|
|
pure $ if hackAroundIncludes
|
|
then
|
|
( ews
|
|
, TextL.intercalate (TextL.pack "\n") $ hackF <$> TextL.splitOn
|
|
(TextL.pack "\n")
|
|
outRaw
|
|
)
|
|
else (ews, outRaw)
|
|
let customErrOrder ErrorInput{} = 4
|
|
customErrOrder LayoutWarning{} = 0 :: Int
|
|
customErrOrder ErrorOutputCheck{} = 1
|
|
customErrOrder ErrorUnusedComment{} = 2
|
|
customErrOrder ErrorUnknownNode{} = 3
|
|
customErrOrder ErrorMacroConfig{} = 5
|
|
let hasErrors =
|
|
if moduleConfig & _conf_errorHandling & _econf_Werror & confUnpack
|
|
then not $ null errsWarns
|
|
else 0 < maximum (-1 : fmap customErrOrder errsWarns)
|
|
if hasErrors
|
|
then throwE $ errsWarns
|
|
else pure $ TextL.toStrict outputTextL
|
|
|
|
|
|
|
|
-- 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.
|
|
pPrintModule
|
|
:: Config
|
|
-> PerItemConfig
|
|
-> ExactPrint.Anns
|
|
-> GHC.ParsedSource
|
|
-> ([BrittanyError], TextL.Text)
|
|
pPrintModule conf inlineConf anns parsedModule =
|
|
let ((out, errs), debugStrings) =
|
|
runIdentity
|
|
$ MultiRWSS.runMultiRWSTNil
|
|
$ MultiRWSS.withMultiWriterAW
|
|
$ MultiRWSS.withMultiWriterAW
|
|
$ MultiRWSS.withMultiWriterW
|
|
$ MultiRWSS.withMultiReader anns
|
|
$ MultiRWSS.withMultiReader conf
|
|
$ MultiRWSS.withMultiReader inlineConf
|
|
$ MultiRWSS.withMultiReader (extractToplevelAnns parsedModule anns)
|
|
$ do
|
|
traceIfDumpConf "bridoc annotations raw" _dconf_dump_annotations
|
|
$ annsDoc anns
|
|
ppModule parsedModule
|
|
tracer = if Seq.null debugStrings
|
|
then id
|
|
else
|
|
trace ("---- DEBUGMESSAGES ---- ")
|
|
. foldr (seq . join trace) id debugStrings
|
|
in tracer $ (errs, Text.Builder.toLazyText out)
|
|
-- unless () $ do
|
|
--
|
|
-- debugStrings `forM_` \s ->
|
|
-- trace s $ return ()
|
|
|
|
-- | Additionally checks that the output compiles again, appending an error
|
|
-- if it does not.
|
|
pPrintModuleAndCheck
|
|
:: Config
|
|
-> PerItemConfig
|
|
-> ExactPrint.Anns
|
|
-> GHC.ParsedSource
|
|
-> IO ([BrittanyError], TextL.Text)
|
|
pPrintModuleAndCheck conf inlineConf anns parsedModule = do
|
|
let ghcOptions = conf & _conf_forward & _options_ghc & runIdentity
|
|
let (errs, output) = pPrintModule conf inlineConf anns parsedModule
|
|
parseResult <- parseModuleFromString ghcOptions
|
|
"output"
|
|
(\_ -> return $ Right ())
|
|
(TextL.unpack output)
|
|
let errs' = errs ++ case parseResult of
|
|
Left{} -> [ErrorOutputCheck]
|
|
Right{} -> []
|
|
return (errs', output)
|
|
|
|
|
|
-- used for testing mostly, currently.
|
|
-- TODO: use parsePrintModule instead and remove this function.
|
|
parsePrintModuleTests :: Config -> String -> Text -> IO (Either String Text)
|
|
parsePrintModuleTests conf filename input = do
|
|
let inputStr = Text.unpack input
|
|
parseResult <- parseModuleFromString
|
|
(conf & _conf_forward & _options_ghc & runIdentity)
|
|
filename
|
|
(const . pure $ Right ())
|
|
inputStr
|
|
case parseResult of
|
|
Left err -> return $ Left err
|
|
Right (anns, parsedModule, _) -> runExceptT $ do
|
|
(inlineConf, perItemConf) <-
|
|
case extractCommentConfigs anns (getTopLevelDeclNameMap parsedModule) of
|
|
Left err -> throwE $ "error in inline config: " ++ show err
|
|
Right x -> pure x
|
|
let moduleConf = cZipWith fromOptionIdentity conf inlineConf
|
|
let omitCheck =
|
|
conf
|
|
& _conf_errorHandling
|
|
.> _econf_omit_output_valid_check
|
|
.> confUnpack
|
|
(errs, ltext) <- if omitCheck
|
|
then return $ pPrintModule moduleConf perItemConf anns parsedModule
|
|
else lift
|
|
$ pPrintModuleAndCheck moduleConf perItemConf anns parsedModule
|
|
if null errs
|
|
then pure $ TextL.toStrict $ ltext
|
|
else
|
|
let
|
|
errStrs = errs <&> \case
|
|
ErrorInput str -> str
|
|
ErrorUnusedComment str -> str
|
|
LayoutWarning str -> str
|
|
ErrorUnknownNode str _ -> str
|
|
ErrorMacroConfig str _ -> "when parsing inline config: " ++ str
|
|
ErrorOutputCheck -> "Output is not syntactically valid."
|
|
in throwE $ "pretty printing error(s):\n" ++ List.unlines errStrs
|
|
|
|
-- this approach would for if there was a pure GHC.parseDynamicFilePragma.
|
|
-- Unfortunately that does not exist yet, so we cannot provide a nominally
|
|
-- pure interface.
|
|
|
|
-- parsePrintModuleTests :: Text -> Either String Text
|
|
-- parsePrintModuleTests input = do
|
|
-- let dflags = GHC.unsafeGlobalDynFlags
|
|
-- let fakeFileName = "SomeTestFakeFileName.hs"
|
|
-- let pragmaInfo = GHC.getOptions
|
|
-- dflags
|
|
-- (GHC.stringToStringBuffer $ Text.unpack input)
|
|
-- fakeFileName
|
|
-- (dflags1, _, _) <- GHC.parseDynamicFilePragma dflags pragmaInfo
|
|
-- let parseResult = ExactPrint.Parsers.parseWith
|
|
-- dflags1
|
|
-- fakeFileName
|
|
-- GHC.parseModule
|
|
-- inputStr
|
|
-- case parseResult of
|
|
-- Left (_, s) -> Left $ "parsing error: " ++ s
|
|
-- Right (anns, parsedModule) -> do
|
|
-- let (out, errs) = runIdentity
|
|
-- $ runMultiRWSTNil
|
|
-- $ Control.Monad.Trans.MultiRWS.Lazy.withMultiWriterAW
|
|
-- $ Control.Monad.Trans.MultiRWS.Lazy.withMultiWriterW
|
|
-- $ Control.Monad.Trans.MultiRWS.Lazy.withMultiReader anns
|
|
-- $ ppModule parsedModule
|
|
-- if (not $ null errs)
|
|
-- then do
|
|
-- let errStrs = errs <&> \case
|
|
-- ErrorUnusedComment str -> str
|
|
-- Left $ "pretty printing error(s):\n" ++ List.unlines errStrs
|
|
-- else return $ TextL.toStrict $ Text.Builder.toLazyText out
|
|
|
|
toLocal :: Config -> ExactPrint.Anns -> PPMLocal a -> PPM a
|
|
toLocal conf anns m = do
|
|
(x, write) <-
|
|
lift $ MultiRWSS.runMultiRWSTAW (conf :+: anns :+: HNil) HNil $ m
|
|
MultiRWSS.mGetRawW >>= \w -> MultiRWSS.mPutRawW (w `mappend` write)
|
|
pure x
|
|
|
|
ppModule :: GenLocated SrcSpan HsModule -> PPM ()
|
|
ppModule lmod@(L _loc _m@(HsModule _ _name _exports _ decls _ _)) = do
|
|
defaultAnns <- do
|
|
anns <- mAsk
|
|
let annKey = ExactPrint.mkAnnKey lmod
|
|
let annMap = Map.findWithDefault Map.empty annKey anns
|
|
let isEof = (== ExactPrint.AnnEofPos)
|
|
let overAnnsDP f a = a { ExactPrint.annsDP = f $ ExactPrint.annsDP a }
|
|
pure $ fmap (overAnnsDP . filter $ isEof . fst) annMap
|
|
|
|
post <- ppPreamble lmod
|
|
decls `forM_` \decl -> do
|
|
let declAnnKey = ExactPrint.mkAnnKey decl
|
|
let declBindingNames = getDeclBindingNames decl
|
|
inlineConf <- mAsk
|
|
let mDeclConf = Map.lookup declAnnKey $ _icd_perKey inlineConf
|
|
let mBindingConfs =
|
|
declBindingNames <&> \n -> Map.lookup n $ _icd_perBinding inlineConf
|
|
filteredAnns <- mAsk
|
|
<&> \annMap ->
|
|
Map.union defaultAnns $
|
|
Map.findWithDefault Map.empty declAnnKey annMap
|
|
|
|
traceIfDumpConf "bridoc annotations filtered/transformed"
|
|
_dconf_dump_annotations
|
|
$ annsDoc filteredAnns
|
|
|
|
config <- mAsk
|
|
|
|
let config' = cZipWith fromOptionIdentity config
|
|
$ mconcat (catMaybes (mBindingConfs ++ [mDeclConf]))
|
|
|
|
let exactprintOnly = config' & _conf_roundtrip_exactprint_only & confUnpack
|
|
toLocal config' filteredAnns $ do
|
|
bd <- if exactprintOnly
|
|
then briDocMToPPM $ briDocByExactNoComment decl
|
|
else do
|
|
(r, errs, debugs) <- briDocMToPPMInner $ layoutDecl decl
|
|
mTell debugs
|
|
mTell errs
|
|
if null errs
|
|
then pure r
|
|
else briDocMToPPM $ briDocByExactNoComment decl
|
|
layoutBriDoc bd
|
|
|
|
let finalComments = filter
|
|
(fst .> \case
|
|
ExactPrint.AnnComment{} -> True
|
|
_ -> False
|
|
)
|
|
post
|
|
post `forM_` \case
|
|
(ExactPrint.AnnComment (ExactPrint.Comment cmStr _ _), l) -> do
|
|
ppmMoveToExactLoc l
|
|
mTell $ Text.Builder.fromString cmStr
|
|
(ExactPrint.AnnEofPos, (ExactPrint.DP (eofZ, eofX))) ->
|
|
let folder (acc, _) (kw, ExactPrint.DP (y, x)) = case kw of
|
|
ExactPrint.AnnComment cm
|
|
| span <- ExactPrint.commentIdentifier cm
|
|
-> ( acc + y + GHC.srcSpanEndLine span - GHC.srcSpanStartLine span
|
|
, x + GHC.srcSpanEndCol span - GHC.srcSpanStartCol span
|
|
)
|
|
_ -> (acc + y, x)
|
|
(cmY, cmX) = foldl' folder (0, 0) finalComments
|
|
in ppmMoveToExactLoc $ ExactPrint.DP (eofZ - cmY, eofX - cmX)
|
|
_ -> return ()
|
|
|
|
getDeclBindingNames :: LHsDecl GhcPs -> [String]
|
|
getDeclBindingNames (L _ decl) = case decl of
|
|
SigD _ (TypeSig _ ns _) -> ns <&> \(L _ n) -> Text.unpack (rdrNameToText n)
|
|
ValD _ (FunBind _ (L _ n) _ _) -> [Text.unpack $ rdrNameToText n]
|
|
_ -> []
|
|
|
|
|
|
-- Prints the information associated with the module annotation
|
|
-- This includes the imports
|
|
ppPreamble
|
|
:: GenLocated SrcSpan HsModule
|
|
-> PPM [(ExactPrint.KeywordId, ExactPrint.DeltaPos)]
|
|
ppPreamble lmod@(L loc m@HsModule{}) = do
|
|
filteredAnns <- mAsk <&> \annMap ->
|
|
Map.findWithDefault Map.empty (ExactPrint.mkAnnKey lmod) annMap
|
|
-- Since ghc-exactprint adds annotations following (implicit)
|
|
-- modules to both HsModule and the elements in the module
|
|
-- this can cause duplication of comments. So strip
|
|
-- attached annotations that come after the module's where
|
|
-- from the module node
|
|
config <- mAsk
|
|
let shouldReformatPreamble =
|
|
config & _conf_layout & _lconfig_reformatModulePreamble & confUnpack
|
|
|
|
let
|
|
(filteredAnns', post) =
|
|
case Map.lookup (ExactPrint.mkAnnKey lmod) filteredAnns of
|
|
Nothing -> (filteredAnns, [])
|
|
Just mAnn ->
|
|
let
|
|
modAnnsDp = ExactPrint.annsDP mAnn
|
|
isWhere (ExactPrint.G AnnWhere) = True
|
|
isWhere _ = False
|
|
isEof (ExactPrint.AnnEofPos) = True
|
|
isEof _ = False
|
|
whereInd = List.findIndex (isWhere . fst) modAnnsDp
|
|
eofInd = List.findIndex (isEof . fst) modAnnsDp
|
|
(pre, post') = case (whereInd, eofInd) of
|
|
(Nothing, Nothing) -> ([], modAnnsDp)
|
|
(Just i , Nothing) -> List.splitAt (i + 1) modAnnsDp
|
|
(Nothing, Just _i) -> ([], modAnnsDp)
|
|
(Just i , Just j ) -> List.splitAt (min (i + 1) j) modAnnsDp
|
|
mAnn' = mAnn { ExactPrint.annsDP = pre }
|
|
filteredAnns'' =
|
|
Map.insert (ExactPrint.mkAnnKey lmod) mAnn' filteredAnns
|
|
in
|
|
(filteredAnns'', post')
|
|
traceIfDumpConf "bridoc annotations filtered/transformed"
|
|
_dconf_dump_annotations
|
|
$ annsDoc filteredAnns'
|
|
|
|
if shouldReformatPreamble
|
|
then toLocal config filteredAnns' $ withTransformedAnns lmod $ do
|
|
briDoc <- briDocMToPPM $ layoutModule lmod
|
|
layoutBriDoc briDoc
|
|
else
|
|
let emptyModule = L loc m { hsmodDecls = [] }
|
|
in MultiRWSS.withMultiReader filteredAnns' $ processDefault emptyModule
|
|
return post
|
|
|
|
_sigHead :: Sig GhcPs -> String
|
|
_sigHead = \case
|
|
TypeSig _ names _ ->
|
|
"TypeSig " ++ intercalate "," (Text.unpack . lrdrNameToText <$> names)
|
|
_ -> "unknown sig"
|
|
|
|
_bindHead :: HsBind GhcPs -> String
|
|
_bindHead = \case
|
|
FunBind _ fId _ [] -> "FunBind " ++ (Text.unpack $ lrdrNameToText $ fId)
|
|
PatBind _ _pat _ ([], []) -> "PatBind smth"
|
|
_ -> "unknown bind"
|
|
|
|
|
|
|
|
layoutBriDoc :: BriDocNumbered -> PPMLocal ()
|
|
layoutBriDoc briDoc = do
|
|
-- first step: transform the briDoc.
|
|
briDoc' <- MultiRWSS.withMultiStateS BDEmpty $ do
|
|
-- Note that briDoc is BriDocNumbered, but state type is BriDoc.
|
|
-- That's why the alt-transform looks a bit special here.
|
|
traceIfDumpConf "bridoc raw" _dconf_dump_bridoc_raw
|
|
$ briDocToDoc
|
|
$ unwrapBriDocNumbered
|
|
$ briDoc
|
|
-- bridoc transformation: remove alts
|
|
transformAlts briDoc >>= mSet
|
|
mGet
|
|
>>= briDocToDoc
|
|
.> traceIfDumpConf "bridoc post-alt" _dconf_dump_bridoc_simpl_alt
|
|
-- bridoc transformation: float stuff in
|
|
mGet >>= transformSimplifyFloating .> mSet
|
|
mGet
|
|
>>= briDocToDoc
|
|
.> traceIfDumpConf "bridoc post-floating"
|
|
_dconf_dump_bridoc_simpl_floating
|
|
-- bridoc transformation: par removal
|
|
mGet >>= transformSimplifyPar .> mSet
|
|
mGet
|
|
>>= briDocToDoc
|
|
.> traceIfDumpConf "bridoc post-par" _dconf_dump_bridoc_simpl_par
|
|
-- bridoc transformation: float stuff in
|
|
mGet >>= transformSimplifyColumns .> mSet
|
|
mGet
|
|
>>= briDocToDoc
|
|
.> traceIfDumpConf "bridoc post-columns" _dconf_dump_bridoc_simpl_columns
|
|
-- bridoc transformation: indent
|
|
mGet >>= transformSimplifyIndent .> mSet
|
|
mGet
|
|
>>= briDocToDoc
|
|
.> traceIfDumpConf "bridoc post-indent" _dconf_dump_bridoc_simpl_indent
|
|
mGet
|
|
>>= briDocToDoc
|
|
.> traceIfDumpConf "bridoc final" _dconf_dump_bridoc_final
|
|
-- -- convert to Simple type
|
|
-- simpl <- mGet <&> transformToSimple
|
|
-- return simpl
|
|
|
|
anns :: ExactPrint.Anns <- mAsk
|
|
|
|
let state = LayoutState { _lstate_baseYs = [0]
|
|
, _lstate_curYOrAddNewline = Right 0 -- important that we dont use left
|
|
-- here because moveToAnn stuff
|
|
-- of the first node needs to do
|
|
-- its thing properly.
|
|
, _lstate_indLevels = [0]
|
|
, _lstate_indLevelLinger = 0
|
|
, _lstate_comments = anns
|
|
, _lstate_commentCol = Nothing
|
|
, _lstate_addSepSpace = Nothing
|
|
, _lstate_commentNewlines = 0
|
|
}
|
|
|
|
state' <- MultiRWSS.withMultiStateS state $ layoutBriDocM briDoc'
|
|
|
|
let remainingComments =
|
|
[ c
|
|
| (ExactPrint.AnnKey _ con, elemAnns) <- Map.toList
|
|
(_lstate_comments state')
|
|
-- With the new import layouter, we manually process comments
|
|
-- without relying on the backend to consume the comments out of
|
|
-- the state/map. So they will end up here, and we need to ignore
|
|
-- them.
|
|
, ExactPrint.unConName con /= "ImportDecl"
|
|
, c <- extractAllComments elemAnns
|
|
]
|
|
remainingComments
|
|
`forM_` (fst .> show .> ErrorUnusedComment .> (: []) .> mTell)
|
|
|
|
return $ ()
|