{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE NoImplicitPrelude #-} module Language.Haskell.Brittany.Internal.BackendUtils where import qualified Data.Data import qualified Data.Either import qualified Data.Map as Map import qualified Data.Maybe import qualified Data.Semigroup as Semigroup import qualified Data.Text as Text import qualified Data.Text.Lazy.Builder as Text.Builder import GHC (Located) import qualified GHC.OldList as List import Language.Haskell.Brittany.Internal.Config.Types import Language.Haskell.Brittany.Internal.Prelude import Language.Haskell.Brittany.Internal.PreludeUtils import Language.Haskell.Brittany.Internal.Types import Language.Haskell.Brittany.Internal.Utils import Language.Haskell.GHC.ExactPrint.Types (AnnKey, Annotation) import qualified Language.Haskell.GHC.ExactPrint.Types as ExactPrint traceLocal :: (MonadMultiState LayoutState m) => a -> m () traceLocal _ = return () layoutWriteAppend :: ( MonadMultiWriter Text.Builder.Builder m , MonadMultiState LayoutState m ) => Text -> m () layoutWriteAppend t = do traceLocal ("layoutWriteAppend", t) state <- mGet case _lstate_curYOrAddNewline state of Right i -> do replicateM_ i $ mTell $ Text.Builder.fromString $ "\n" Left{} -> do return () let spaces = fromMaybe 0 $ _lstate_addSepSpace state mTell $ Text.Builder.fromText $ Text.pack (replicate spaces ' ') mTell $ Text.Builder.fromText $ t mModify $ \s -> s { _lstate_curYOrAddNewline = Left $ case _lstate_curYOrAddNewline s of Left c -> c + Text.length t + spaces Right{} -> Text.length t + spaces , _lstate_addSepSpace = Nothing } layoutWriteAppendSpaces :: ( MonadMultiWriter Text.Builder.Builder m , MonadMultiState LayoutState m ) => Int -> m () layoutWriteAppendSpaces i = do traceLocal ("layoutWriteAppendSpaces", i) unless (i == 0) $ do state <- mGet mSet $ state { _lstate_addSepSpace = Just $ maybe i (+i) $ _lstate_addSepSpace state } layoutWriteAppendMultiline :: ( MonadMultiWriter Text.Builder.Builder m , MonadMultiState LayoutState m ) => [Text] -> m () layoutWriteAppendMultiline ts = do traceLocal ("layoutWriteAppendMultiline", ts) case ts of [] -> layoutWriteAppend (Text.pack "") -- need to write empty, too. (l:lr) -> do layoutWriteAppend l lr `forM_` \x -> do layoutWriteNewline layoutWriteAppend x -- adds a newline and adds spaces to reach the base column. layoutWriteNewlineBlock :: ( MonadMultiWriter Text.Builder.Builder m , MonadMultiState LayoutState m ) => m () layoutWriteNewlineBlock = do traceLocal ("layoutWriteNewlineBlock") state <- mGet mSet $ state { _lstate_curYOrAddNewline = Right 1 , _lstate_addSepSpace = Just $ lstate_baseY state } -- layoutMoveToIndentCol :: ( MonadMultiState LayoutState m -- , MonadMultiWriter (Seq String) m) => Int -> m () -- layoutMoveToIndentCol i = do -- #if INSERTTRACES -- tellDebugMessShow ("layoutMoveToIndentCol", i) -- #endif -- state <- mGet -- mSet $ state -- { _lstate_addSepSpace = Just -- $ if isJust $ _lstate_addNewline state -- then i -- else _lstate_indLevelLinger state + i - _lstate_curY state -- } layoutSetCommentCol :: (MonadMultiState LayoutState m) => m () layoutSetCommentCol = do state <- mGet let col = case _lstate_curYOrAddNewline state of Left i -> i + fromMaybe 0 (_lstate_addSepSpace state) Right{} -> lstate_baseY state traceLocal ("layoutSetCommentCol", col) unless (Data.Maybe.isJust $ _lstate_commentCol state) $ mSet state { _lstate_commentCol = Just col } -- This is also used to move to non-comments in a couple of places. Seems -- to be harmless so far.. layoutMoveToCommentPos :: ( MonadMultiWriter Text.Builder.Builder m , MonadMultiState LayoutState m ) => Int -> Int -> Int -> m () layoutMoveToCommentPos y x commentLines = do traceLocal ("layoutMoveToCommentPos", y, x, commentLines) state <- mGet mSet state { _lstate_curYOrAddNewline = case _lstate_curYOrAddNewline state of Left i -> if y == 0 then Left i else Right y Right{} -> Right y , _lstate_addSepSpace = Just $ if Data.Maybe.isJust (_lstate_commentCol state) then case _lstate_curYOrAddNewline state of Left{} -> if y == 0 then x else _lstate_indLevelLinger state + x Right{} -> _lstate_indLevelLinger state + x else if y == 0 then x else _lstate_indLevelLinger state + x , _lstate_commentCol = Just $ case _lstate_commentCol state of Just existing -> existing Nothing -> case _lstate_curYOrAddNewline state of Left i -> i + fromMaybe 0 (_lstate_addSepSpace state) Right{} -> lstate_baseY state , _lstate_commentNewlines = _lstate_commentNewlines state + y + commentLines - 1 } -- | does _not_ add spaces to again reach the current base column. layoutWriteNewline :: ( MonadMultiWriter Text.Builder.Builder m , MonadMultiState LayoutState m ) => m () layoutWriteNewline = do traceLocal ("layoutWriteNewline") state <- mGet mSet $ state { _lstate_curYOrAddNewline = case _lstate_curYOrAddNewline state of Left{} -> Right 1 Right i -> Right (i + 1) , _lstate_addSepSpace = Nothing } _layoutResetCommentNewlines :: MonadMultiState LayoutState m => m () _layoutResetCommentNewlines = do mModify $ \state -> state { _lstate_commentNewlines = 0 } layoutWriteEnsureNewlineBlock :: ( MonadMultiWriter Text.Builder.Builder m , MonadMultiState LayoutState m ) => m () layoutWriteEnsureNewlineBlock = do traceLocal ("layoutWriteEnsureNewlineBlock") state <- mGet mSet $ state { _lstate_curYOrAddNewline = case _lstate_curYOrAddNewline state of Left{} -> Right 1 Right i -> Right $ max 1 i , _lstate_addSepSpace = Just $ lstate_baseY state , _lstate_commentCol = Nothing } layoutWriteEnsureAbsoluteN :: ( MonadMultiWriter Text.Builder.Builder m , MonadMultiState LayoutState m ) => Int -> m () layoutWriteEnsureAbsoluteN n = do state <- mGet let diff = case (_lstate_commentCol state, _lstate_curYOrAddNewline state) of (Just c , _ ) -> n - c (Nothing, Left i ) -> n - i (Nothing, Right{}) -> n traceLocal ("layoutWriteEnsureAbsoluteN", n, diff) when (diff > 0) $ do mSet $ state { _lstate_addSepSpace = Just diff -- this always sets to -- at least (Just 1), so we won't -- overwrite any old value in any -- bad way. } layoutBaseYPushInternal :: (MonadMultiState LayoutState m) => Int -> m () layoutBaseYPushInternal i = do traceLocal ("layoutBaseYPushInternal", i) mModify $ \s -> s { _lstate_baseYs = i : _lstate_baseYs s } layoutBaseYPopInternal :: (MonadMultiState LayoutState m) => m () layoutBaseYPopInternal = do traceLocal ("layoutBaseYPopInternal") mModify $ \s -> s { _lstate_baseYs = List.tail $ _lstate_baseYs s } layoutIndentLevelPushInternal :: (MonadMultiState LayoutState m) => Int -> m () layoutIndentLevelPushInternal i = do traceLocal ("layoutIndentLevelPushInternal", i) mModify $ \s -> s { _lstate_indLevelLinger = lstate_indLevel s , _lstate_indLevels = i : _lstate_indLevels s } layoutIndentLevelPopInternal :: (MonadMultiState LayoutState m) => m () layoutIndentLevelPopInternal = do traceLocal ("layoutIndentLevelPopInternal") mModify $ \s -> s { _lstate_indLevelLinger = lstate_indLevel s , _lstate_indLevels = List.tail $ _lstate_indLevels s } layoutRemoveIndentLevelLinger :: ( MonadMultiState LayoutState m) => m () layoutRemoveIndentLevelLinger = do mModify $ \s -> s { _lstate_indLevelLinger = lstate_indLevel s } layoutWithAddBaseCol :: ( MonadMultiWriter Text.Builder.Builder m , MonadMultiState LayoutState m , MonadMultiReader Config m ) => m () -> m () layoutWithAddBaseCol m = do amount <- mAsk <&> _conf_layout .> _lconfig_indentAmount .> confUnpack state <- mGet layoutBaseYPushInternal $ lstate_baseY state + amount m layoutBaseYPopInternal layoutWithAddBaseColBlock :: ( MonadMultiWriter Text.Builder.Builder m , MonadMultiState LayoutState m , MonadMultiReader Config m ) => m () -> m () layoutWithAddBaseColBlock m = do amount <- mAsk <&> _conf_layout .> _lconfig_indentAmount .> confUnpack state <- mGet layoutBaseYPushInternal $ lstate_baseY state + amount layoutWriteEnsureBlock m layoutBaseYPopInternal layoutWithAddBaseColNBlock :: ( MonadMultiWriter Text.Builder.Builder m , MonadMultiState LayoutState m ) => Int -> m () -> m () layoutWithAddBaseColNBlock amount m = do traceLocal ("layoutWithAddBaseColNBlock", amount) state <- mGet layoutBaseYPushInternal $ lstate_baseY state + amount layoutWriteEnsureBlock m layoutBaseYPopInternal layoutWriteEnsureBlock :: ( MonadMultiWriter Text.Builder.Builder m , MonadMultiState LayoutState m ) => m () layoutWriteEnsureBlock = do traceLocal ("layoutWriteEnsureBlock") state <- mGet let diff = case (_lstate_addSepSpace state, _lstate_curYOrAddNewline state) of (Nothing, Left i ) -> lstate_baseY state - i (Nothing, Right{}) -> lstate_baseY state (Just sp, Left i ) -> max sp (lstate_baseY state - i) (Just sp, Right{}) -> max sp (lstate_baseY state) -- when (diff>0) $ layoutWriteNewlineBlock when (diff > 0) $ do mSet $ state { _lstate_addSepSpace = Just $ diff } layoutWithAddBaseColN :: ( MonadMultiWriter Text.Builder.Builder m , MonadMultiState LayoutState m ) => Int -> m () -> m () layoutWithAddBaseColN amount m = do state <- mGet layoutBaseYPushInternal $ lstate_baseY state + amount m layoutBaseYPopInternal layoutBaseYPushCur :: (MonadMultiState LayoutState m) => m () layoutBaseYPushCur = do traceLocal ("layoutBaseYPushCur") state <- mGet case _lstate_commentCol state of Nothing -> case (_lstate_curYOrAddNewline state, _lstate_addSepSpace state) of (Left i , Just j ) -> layoutBaseYPushInternal (i + j) (Left i , Nothing) -> layoutBaseYPushInternal i (Right{}, _ ) -> layoutBaseYPushInternal $ lstate_baseY state Just cCol -> layoutBaseYPushInternal cCol layoutBaseYPop :: (MonadMultiState LayoutState m) => m () layoutBaseYPop = do traceLocal ("layoutBaseYPop") layoutBaseYPopInternal layoutIndentLevelPushCur :: (MonadMultiState LayoutState m) => m () layoutIndentLevelPushCur = do traceLocal ("layoutIndentLevelPushCur") state <- mGet let y = case (_lstate_curYOrAddNewline state, _lstate_addSepSpace state) of (Left i , Just j ) -> i + j (Left i , Nothing) -> i (Right{}, Just j ) -> j (Right{}, Nothing) -> 0 layoutIndentLevelPushInternal y layoutIndentLevelPop :: (MonadMultiState LayoutState m) => m () layoutIndentLevelPop = do traceLocal ("layoutIndentLevelPop") layoutIndentLevelPopInternal -- why are comment indentations relative to the previous indentation on -- the first node of an additional indentation, and relative to the outer -- indentation after the last node of some indented stuff? sure does not -- make sense. layoutRemoveIndentLevelLinger layoutAddSepSpace :: (MonadMultiState LayoutState m) => m () layoutAddSepSpace = do state <- mGet mSet $ state { _lstate_addSepSpace = Just $ fromMaybe 1 $ _lstate_addSepSpace state } -- TODO: when refactoring is complete, the other version of this method -- can probably be removed. moveToExactAnn :: ( MonadMultiWriter Text.Builder.Builder m , MonadMultiState LayoutState m , MonadMultiReader (Map AnnKey Annotation) m ) => AnnKey -> m () moveToExactAnn annKey = do traceLocal ("moveToExactAnn", annKey) anns <- mAsk case Map.lookup annKey anns of Nothing -> return () Just ann -> do -- curY <- mGet <&> _lstate_curY let ExactPrint.DP (y, _x) = ExactPrint.annEntryDelta ann -- mModify $ \state -> state { _lstate_addNewline = Just x } moveToY y moveToY :: MonadMultiState LayoutState m => Int -> m () moveToY y = mModify $ \state -> let upd = case _lstate_curYOrAddNewline state of Left i -> if y == 0 then Left i else Right y Right i -> Right $ max y i in state { _lstate_curYOrAddNewline = upd , _lstate_addSepSpace = if Data.Either.isRight upd then _lstate_commentCol state <|> _lstate_addSepSpace state <|> Just (lstate_baseY state) else Nothing , _lstate_commentCol = Nothing } -- fixMoveToLineByIsNewline :: MonadMultiState -- LayoutState m => Int -> m Int -- fixMoveToLineByIsNewline x = do -- newLineState <- mGet <&> _lstate_isNewline -- return $ if newLineState == NewLineStateYes -- then x-1 -- else x ppmMoveToExactLoc :: MonadMultiWriter Text.Builder.Builder m => ExactPrint.DeltaPos -> m () ppmMoveToExactLoc (ExactPrint.DP (x, y)) = do replicateM_ x $ mTell $ Text.Builder.fromString "\n" replicateM_ y $ mTell $ Text.Builder.fromString " " -- TODO: update and use, or clean up. Currently dead code. layoutWritePriorComments :: ( Data.Data.Data ast , MonadMultiWriter Text.Builder.Builder m , MonadMultiState LayoutState m ) => Located ast -> m () layoutWritePriorComments ast = do mAnn <- do state <- mGet let key = ExactPrint.mkAnnKey ast let anns = _lstate_comments state let mAnn = ExactPrint.annPriorComments <$> Map.lookup key anns mSet $ state { _lstate_comments = Map.adjust (\ann -> ann { ExactPrint.annPriorComments = [] }) key anns } return mAnn case mAnn of Nothing -> return () Just priors -> do unless (null priors) $ layoutSetCommentCol priors `forM_` \( ExactPrint.Comment comment _ _ , ExactPrint.DP (x, y) ) -> do replicateM_ x layoutWriteNewline layoutWriteAppendSpaces y layoutWriteAppendMultiline $ Text.lines $ Text.pack comment -- TODO: update and use, or clean up. Currently dead code. -- this currently only extracs from the `annsDP` field of Annotations. -- per documentation, this seems sufficient, as the -- "..`annFollowingComments` are only added by AST transformations ..". layoutWritePostComments :: (Data.Data.Data ast, MonadMultiWriter Text.Builder.Builder m, MonadMultiState LayoutState m) => Located ast -> m () layoutWritePostComments ast = do mAnn <- do state <- mGet let key = ExactPrint.mkAnnKey ast let anns = _lstate_comments state let mAnn = ExactPrint.annFollowingComments <$> Map.lookup key anns mSet $ state { _lstate_comments = Map.adjust (\ann -> ann { ExactPrint.annFollowingComments = [] }) key anns } return mAnn case mAnn of Nothing -> return () Just posts -> do unless (null posts) $ layoutSetCommentCol posts `forM_` \( ExactPrint.Comment comment _ _ , ExactPrint.DP (x, y) ) -> do replicateM_ x layoutWriteNewline layoutWriteAppend $ Text.pack $ replicate y ' ' mModify $ \s -> s { _lstate_addSepSpace = Nothing } layoutWriteAppendMultiline $ Text.lines $ Text.pack $ comment layoutIndentRestorePostComment :: ( MonadMultiState LayoutState m , MonadMultiWriter Text.Builder.Builder m ) => m () layoutIndentRestorePostComment = do state <- mGet let mCommentCol = _lstate_commentCol state let eCurYAddNL = _lstate_curYOrAddNewline state mModify $ \s -> s { _lstate_commentCol = Nothing , _lstate_commentNewlines = 0 } case (mCommentCol, eCurYAddNL) of (Just commentCol, Left{}) -> do layoutWriteEnsureNewlineBlock layoutWriteEnsureAbsoluteN $ commentCol + fromMaybe 0 (_lstate_addSepSpace state) _ -> return () -- layoutWritePriorCommentsRestore :: (Data.Data.Data ast, -- MonadMultiWriter Text.Builder.Builder m, -- MonadMultiState LayoutState m -- , MonadMultiWriter (Seq String) m) -- => Located ast -> m () -- layoutWritePriorCommentsRestore x = do -- layoutWritePriorComments x -- layoutIndentRestorePostComment -- -- layoutWritePostCommentsRestore :: (Data.Data.Data ast, -- MonadMultiWriter Text.Builder.Builder m, -- MonadMultiState LayoutState m -- , MonadMultiWriter (Seq String) m) -- => Located ast -> m () -- layoutWritePostCommentsRestore x = do -- layoutWritePostComments x -- layoutIndentRestorePostComment