{-# LANGUAGE LambdaCase #-} {-# LANGUAGE MonadComprehensions #-} {-# LANGUAGE NoImplicitPrelude #-} module Language.Haskell.Brittany.Internal.ToBriDoc.Expr where import qualified Data.Sequence as Seq import qualified Data.Text as Text import GHC (GenLocated(L), RdrName(..)) import qualified GHC.Data.FastString as FastString import GHC.Types.SourceText (IntegralLit(IL), FractionalLit(FL), SourceText(SourceText)) import GHC.Hs import qualified GHC.Types.SrcLoc as GHC import qualified GHC.OldList as List import GHC.Types.Basic import GHC.Types.Name import Language.Haskell.Brittany.Internal.Config.Types import Language.Haskell.Brittany.Internal.S3_ToBriDocTools import Language.Haskell.Brittany.Internal.ToBriDoc.Decl import Language.Haskell.Brittany.Internal.ToBriDoc.Pattern import {-# SOURCE #-} Language.Haskell.Brittany.Internal.ToBriDoc.Stmt import Language.Haskell.Brittany.Internal.ToBriDoc.Type import Language.Haskell.Brittany.Internal.Prelude import Language.Haskell.Brittany.Internal.Types import Language.Haskell.Brittany.Internal.Utils import Language.Haskell.Brittany.Internal.Components.BriDoc layoutExpr :: ToBriDoc HsExpr layoutExpr lexpr@(L _ expr) = do indentPolicy <- mAsk <&> _conf_layout .> _lconfig_indentPolicy .> confUnpack let allowFreeIndent = indentPolicy == IndentPolicyFree docHandleComms lexpr $ case expr of HsVar NoExtField vname -> docHandleComms lexpr $ do docLit =<< lrdrNameToTextAnn vname HsUnboundVar epAnn oname -> docHandleComms epAnn $ do docLit $ Text.pack $ occNameString oname HsRecFld{} -> docHandleComms lexpr $ do -- TODO briDocByExactInlineOnly "HsRecFld" lexpr HsOverLabel _ext name -> -- TODO92 let label = FastString.unpackFS name in docLit . Text.pack $ '#' : label HsIPVar _ext (HsIPName name) -> -- TODO92 let label = FastString.unpackFS name in docLit . Text.pack $ '?' : label HsOverLit epAnn olit -> docHandleComms epAnn $ do allocateNode $ overLitValBriDoc $ ol_val olit HsLit epAnn lit -> docHandleComms epAnn $ do allocateNode $ litBriDoc lit HsLam _ (MG _ (L _ [(L _ match)]) _) | pats <- m_pats match , GRHSs _ [lgrhs] llocals <- m_grhss match , EmptyLocalBinds{} <- llocals , L _ (GRHS epAnn [] body) <- lgrhs -> do patDocs <- zip (True : repeat False) pats `forM` \(isFirst, p) -> fmap return $ do -- this code could be as simple as `colsWrapPat =<< layoutPat p` -- if it was not for the following two cases: -- \ !x -> x -- \ ~x -> x -- These make it necessary to special-case an additional separator. -- (TODO: we create a BDCols here, but then make it ineffective -- by wrapping it in docSeq below. We _could_ add alignments for -- stuff like lists-of-lambdas. Nothing terribly important..) let shouldPrefixSeparator = case p of L _ LazyPat{} -> isFirst L _ BangPat{} -> isFirst _ -> False patDocSeq <- layoutPat p fixed <- case Seq.viewl patDocSeq of p1 Seq.:< pr | shouldPrefixSeparator -> do p1' <- docSeq [docSeparator, pure p1] pure (p1' Seq.<| pr) _ -> pure patDocSeq colsWrapPat fixed bodyDoc <- shareDoc $ docAddBaseY BrIndentRegular $ docHandleComms epAnn $ layoutExpr body let funcPatternPartLine = docCols ColCasePattern (patDocs <&> (\p -> docSeq [docForceSingleline p, docSeparator])) docAlt [ -- single line docSeq [ docLit $ Text.pack "\\" , docForceSingleline funcPatternPartLine , appSep $ docLit $ Text.pack "->" , docForceSingleline bodyDoc ] -- double line , docSetParSpacing $ docAddBaseY BrIndentRegular $ docPar (docSeq [ docLit $ Text.pack "\\" , appSep $ docForceSingleline funcPatternPartLine , docLit $ Text.pack "->" ] ) (docForceSingleline bodyDoc) -- wrapped par spacing , docSetParSpacing $ docSeq [ docLit $ Text.pack "\\" , docForceSingleline funcPatternPartLine , appSep $ docLit $ Text.pack "->" , docForceParSpacing bodyDoc ] -- conservative , docSetParSpacing $ docAddBaseY BrIndentRegular $ docPar (docSeq [ docLit $ Text.pack "\\" , appSep $ docForceSingleline funcPatternPartLine , docLit $ Text.pack "->" ] ) (docNonBottomSpacing bodyDoc) ] HsLam{} -> unknownNodeError "HsLam too complex" lexpr HsLamCase _ (MG _ (L _ []) _) -> do docSetParSpacing $ docAddBaseY BrIndentRegular $ (docLit $ Text.pack "\\case {}") HsLamCase _ (MG _ _lmatches@(L _ matches) _) -> do binderDoc <- docLit $ Text.pack "->" funcPatDocs <- -- docWrapNode lmatches layoutPatternBind Nothing binderDoc `mapM` matches docSetParSpacing $ docAddBaseY BrIndentRegular $ docPar (docLit $ Text.pack "\\case") (docSetBaseAndIndent $ docNonBottomSpacing $ docLines $ return <$> funcPatDocs ) HsApp _ exp1 _ -> do let gather :: [(EpAnnCO, LHsExpr GhcPs)] -> LHsExpr GhcPs -> (LHsExpr GhcPs, [(EpAnnCO, LHsExpr GhcPs)]) gather list = \case L _ (HsApp epAnn l r) -> gather ((epAnn, r) : list) l x -> (x, list) let (headE, paramEs) = gather [] lexpr let colsOrSequence = case headE of L _ (HsVar _ (L _ (Unqual occname))) -> docCols (ColApp $ Text.pack $ occNameString occname) _ -> docSeq headDoc <- shareDoc $ layoutExpr headE paramDocs <- forM paramEs $ \(epAnn, e) -> shareDoc $ docHandleComms epAnn $ layoutExpr e let hasComments = hasAnyCommentsConnected exp1 runFilteredAlternative $ do -- foo x y addAlternativeCond (not hasComments) $ colsOrSequence $ appSep (docForceSingleline headDoc) : spacifyDocs (docForceSingleline <$> paramDocs) -- foo x do -- a -- b -- foo x \meow -> do -- a -- b addAlternativeCond (not hasComments) $ docSetParSpacing $ docSeq [ appSep (docForceSingleline headDoc) , case splitFirstLast paramDocs of FirstLastEmpty -> docEmpty FirstLastSingleton e1 -> docForceParSpacing e1 FirstLast e1 ems eN -> docSeq ( spacifyDocs (docForceSingleline <$> (e1:ems)) ++ [docSeparator, docForceParSpacing eN] ) ] -- foo x -- y addAlternativeCond allowFreeIndent $ docSeq [ appSep (docForceSingleline headDoc) , docSetBaseY $ docAddBaseY BrIndentRegular $ docLines $ docForceSingleline <$> paramDocs ] -- foo -- x -- y addAlternative $ do let checkAllowPar = \case (_, L _ ExplicitTuple{}) -> True (_, L _ ExplicitList{}) -> True (_, L _ HsPar{}) -> True _ -> False let wrap = if all checkAllowPar paramEs then docSetParSpacing else id wrap $ docAddBaseY BrIndentRegular $ docPar (docForceSingleline headDoc) (docNonBottomSpacing $ docLines paramDocs) -- ( multi -- line -- function -- ) -- x -- y addAlternative $ docAddBaseY BrIndentRegular $ docPar headDoc (docNonBottomSpacing $ docLines paramDocs) HsAppType _ exp1 (HsWC _ ty1) -> do t <- shareDoc $ layoutType ty1 e <- shareDoc $ layoutExpr exp1 docAlt [ docSeq [ docForceSingleline e , docSeparator , docLit $ Text.pack "@" , docForceSingleline t ] , docPar e (docSeq [docLit $ Text.pack "@", t]) ] OpApp _topEpAnn expLeft@(L _ OpApp{}) expOp expRight -> do let allowPar = case (expOp, expRight) of (L _ (HsVar _ (L _ (Unqual occname))), _) | occNameString occname == "$" -> True (_, L _ (HsApp _ _ (L _ HsVar{}))) -> False _ -> True let gather :: Bool -> [(ToBriDocM BriDocNumbered, ToBriDocM BriDocNumbered, Bool)] -> LHsExpr GhcPs -> ( ToBriDocM BriDocNumbered , [(ToBriDocM BriDocNumbered, ToBriDocM BriDocNumbered, Bool)] ) gather last opExprList = \case (L _ (OpApp epAnn l1 op1 r1)) -> gather False ( ( docHandleComms epAnn $ layoutExpr op1 , layoutExpr r1 , last ) : opExprList ) l1 final -> (layoutExpr final, opExprList) (leftOperand, appList) = gather True [] lexpr leftOperandDoc <- shareDoc leftOperand appListDocs <- appList `forM` \(x, y, last) -> [ (xD, yD, last) | xD <- shareDoc x , yD <- shareDoc y ] let allowSinglelinePar = not (hasAnyCommentsConnected expLeft) && not (hasAnyCommentsConnected expOp) runFilteredAlternative $ do -- > one + two + three -- or -- > one + two + case x of -- > _ -> three addAlternativeCond allowSinglelinePar $ docSeq [ appSep $ docForceSingleline leftOperandDoc , docSeq $ appListDocs <&> \(od, ed, last) -> docSeq [ appSep $ docForceSingleline od , if last then if allowPar then docForceParSpacing ed else docForceSingleline ed else appSep $ docForceSingleline ed ] ] -- this case rather leads to some unfortunate layouting than to anything -- useful; disabling for now. (it interfers with cols stuff.) -- addAlternative -- $ docSetBaseY -- $ docPar -- leftOperandDoc -- ( docLines -- $ (appListDocs <&> \(od, ed) -> docCols ColOpPrefix [appSep od, docSetBaseY ed]) -- ++ [docCols ColOpPrefix [appSep opLastDoc, docSetBaseY expLastDoc]] -- ) -- > one -- > + two -- > + three addAlternative $ docPar leftOperandDoc (docLines $ appListDocs <&> \(od, ed, _) -> docCols ColOpPrefix [appSep od, docSetBaseY ed] ) OpApp epAnn expLeft expOp expRight -> docHandleComms epAnn $ do expDocLeft <- shareDoc $ layoutExpr expLeft expDocOp <- shareDoc $ layoutExpr expOp expDocRight <- shareDoc $ layoutExpr expRight let allowPar = case (expOp, expRight) of (L _ (HsVar _ (L _ (Unqual occname))), _) | occNameString occname == "$" -> True (_, L _ (HsApp _ _ (L _ HsVar{}))) -> False _ -> True let leftIsDoBlock = case expLeft of L _ HsDo{} -> True _ -> False runFilteredAlternative $ do -- one-line addAlternative $ docSeq [ appSep $ docForceSingleline expDocLeft , appSep $ docForceSingleline expDocOp , docForceSingleline expDocRight ] -- -- line + freely indented block for right expression -- addAlternative -- $ docSeq -- [ appSep $ docForceSingleline expDocLeft -- , appSep $ docForceSingleline expDocOp -- , docSetBaseY $ docAddBaseY BrIndentRegular expDocRight -- ] -- two-line addAlternative $ do let expDocOpAndRight = docForceSingleline $ docCols ColOpPrefix [appSep $ expDocOp, docSetBaseY expDocRight] if leftIsDoBlock then docLines [expDocLeft, expDocOpAndRight] else docAddBaseY BrIndentRegular $ docPar expDocLeft expDocOpAndRight -- TODO: in both cases, we don't force expDocLeft to be -- single-line, which has certain.. interesting consequences. -- At least, the "two-line" label is not entirely -- accurate. -- one-line + par addAlternativeCond allowPar $ docSeq [ appSep $ docForceSingleline expDocLeft , appSep $ docForceSingleline expDocOp , docForceParSpacing expDocRight ] -- more lines addAlternative $ do let expDocOpAndRight = docCols ColOpPrefix [appSep expDocOp, docSetBaseY expDocRight] if leftIsDoBlock then docLines [expDocLeft, expDocOpAndRight] else docAddBaseY BrIndentRegular $ docPar expDocLeft expDocOpAndRight NegApp _ op _ -> do opDoc <- shareDoc $ layoutExpr op docSeq [docLit $ Text.pack "-", opDoc] HsPar epAnn innerExp -> docHandleComms epAnn $ do let AnnParen _ spanOpen spanClose = anns epAnn let wrapOpen = docHandleComms spanOpen let wrapClose = docHandleComms spanClose innerExpDoc <- shareDoc $ layoutExpr innerExp docAlt [ docSeq [ wrapOpen $ docLit $ Text.pack "(" , docForceSingleline innerExpDoc , wrapClose $ docLit $ Text.pack ")" ] , docSetBaseY $ docLines [ docCols ColOpPrefix [ wrapOpen $ docLit $ Text.pack "(" , docAddBaseY (BrIndentSpecial 2) innerExpDoc ] , wrapClose $ docLit $ Text.pack ")" ] ] SectionL _ left op -> do -- TODO: add to testsuite leftDoc <- shareDoc $ layoutExpr left opDoc <- shareDoc $ layoutExpr op docSeq [leftDoc, docSeparator, opDoc] SectionR _ op right -> do -- TODO: add to testsuite opDoc <- shareDoc $ layoutExpr op rightDoc <- shareDoc $ layoutExpr right docSeq [opDoc, docSeparator, rightDoc] ExplicitTuple epAnn args boxity -> docHandleComms epAnn $ do let (wrapOpen, wrapClose) = case anns epAnn of [open, close] -> case boxity of Boxed -> ( docHandleComms $ obtainAnnPos open AnnOpenP , docHandleComms $ obtainAnnPos close AnnCloseP ) Unboxed -> ( docHandleComms $ obtainAnnPos open AnnOpenPH , docHandleComms $ obtainAnnPos close AnnClosePH ) _ -> (id, id) argDocs <- forM args $ \case Present _ e -> shareDoc $ docHandleListElemComms layoutExpr e Missing missingEpAnn -> shareDoc $ docHandleComms missingEpAnn docEmpty -- let ((c1, argsWithC, c2), cRemain) = case epAnn of -- EpAnn _ [open, close] comms -> -- enterCommentsSplitC comms $ do -- comms1 <- getCommentsBeforeKW open AnnOpenP -- elems' <- args `forM` \arg -> case arg of -- Present _ e@(L (SrcSpanAnn elEpAnn loc) _) -> do -- commsB <- case loc of -- GHC.RealSrcSpan span _ -> getCommentsBeforeSpan span -- _ -> pure [] -- case elEpAnn of -- EpAnn _ (AnnListItem items) _ -> do -- commsA <- items `forM` \case -- AddCommaAnn span -> -- getCommentsBeforeEpaLocation span -- ann1 -> -- error $ "unexpected TrailingAnn: " -- ++ showSDocUnsafe (ppr ann1) -- pure $ docWrapNode (commsB, join commsA) $ layoutExpr e -- EpAnnNotUsed -> do -- pure $ prependComments commsB $ layoutExpr e -- Missing (EpAnn _ epa _) -> do -- commsB <- getCommentsBeforeEpaLocation epa -- pure $ prependComments commsB docEmpty -- Missing EpAnnNotUsed -> pure $ docEmpty -- comm2 <- getCommentsBeforeKW close AnnCloseP -- pure (comms1, elems', comm2) -- EpAnn _ _ _ -> error "unexpected ExplicitTuple ann!" -- EpAnnNotUsed -> -- let argsDocs = [ case arg of -- Present _ e -> layoutExpr e -- Missing _ -> docEmpty -- | arg <- args ] -- in (([], argsDocs, []), []) let hasComments = hasAnyCommentsBelow lexpr -- TODO92 this is slightly -- overzealous for comments before open & after close let (openLit, closeLit) = case boxity of Boxed -> ( wrapOpen $ docLit $ Text.pack "(" , wrapClose $ docLit $ Text.pack ")" ) Unboxed -> ( wrapOpen $ docParenHashLSep , wrapClose $ docParenHashRSep ) case splitFirstLast argDocs of FirstLastEmpty -> docSeq [openLit, closeLit] FirstLastSingleton e -> docAlt [ docCols ColTuple [ openLit , docForceSingleline e , closeLit ] , docSetBaseY $ docLines [ docSeq [ openLit , docForceSingleline e ] , closeLit ] ] FirstLast e1 ems eN -> runFilteredAlternative $ do addAlternativeCond (not hasComments) $ docCols ColTuple $ [docSeq [openLit, docForceSingleline e1]] ++ (ems <&> \e -> docSeq [docCommaSep, docForceSingleline e]) ++ [ docSeq [ docCommaSep , -- TODO92 docNodeAnnKW lexpr (Just AnnOpenP) (docForceSingleline eN) , closeLit ] ] addAlternative $ let start = docCols ColTuples [appSep openLit, e1] linesM = ems <&> \d -> docCols ColTuples [docCommaSep, d] lineN = docCols ColTuples [docCommaSep, -- TODO92 docNodeAnnKW lexpr (Just AnnOpenP) eN] end = closeLit in docSetBaseY $ docLines $ [start] ++ linesM ++ [lineN, end] HsCase epAnn cExp (MG _ (L _ []) _) -> docHandleComms epAnn $ do cExpDoc <- shareDoc $ layoutExpr cExp docAlt [ docAddBaseY BrIndentRegular $ docSeq [ appSep $ docLit $ Text.pack "case" , appSep $ docForceSingleline cExpDoc , docLit $ Text.pack "of {}" ] , docPar (docAddBaseY BrIndentRegular $ docPar (docLit $ Text.pack "case") cExpDoc ) (docLit $ Text.pack "of {}") ] HsCase epAnn cExp (MG _ _lmatches@(L _ matches) _) -> docHandleComms epAnn $ do cExpDoc <- shareDoc $ layoutExpr cExp binderDoc <- docLit $ Text.pack "->" funcPatDocs <- -- docWrapNode lmatches layoutPatternBind Nothing binderDoc `mapM` matches docAlt [ docSetParSpacing $ docAddBaseY BrIndentRegular $ docPar (docSeq [ appSep $ docLit $ Text.pack "case" , appSep $ docForceSingleline cExpDoc , docLit $ Text.pack "of" ] ) (docSetBaseAndIndent $ docNonBottomSpacing $ docLines $ return <$> funcPatDocs ) , docPar (docAddBaseY BrIndentRegular $ docPar (docLit $ Text.pack "case") cExpDoc ) (docAddBaseY BrIndentRegular $ docPar (docLit $ Text.pack "of") (docSetBaseAndIndent $ docNonBottomSpacing $ docLines $ return <$> funcPatDocs ) ) ] HsIf epAnn ifExpr thenExpr elseExpr -> docHandleComms epAnn $ do let AnnsIf spanIf spanThen spanElse _ _ = anns epAnn let ifDoc = docHandleComms spanIf $ docLit $ Text.pack "if" let thenDoc = docHandleComms spanThen $ docLit $ Text.pack "then" let elseDoc = docHandleComms spanElse $ docLit $ Text.pack "else" ifExprDoc <- shareDoc $ layoutExpr ifExpr thenExprDoc <- shareDoc $ layoutExpr thenExpr elseExprDoc <- shareDoc $ layoutExpr elseExpr let hasComments = hasAnyCommentsBelow lexpr let maySpecialIndent = case indentPolicy of IndentPolicyLeft -> BrIndentRegular IndentPolicyMultiple -> BrIndentRegular IndentPolicyFree -> BrIndentSpecial 3 -- TODO: some of the alternatives (especially last and last-but-one) -- overlap. docSetIndentLevel $ runFilteredAlternative $ do -- if _ then _ else _ addAlternativeCond (not hasComments) $ docSeq [ appSep $ ifDoc , appSep $ docForceSingleline ifExprDoc , appSep $ thenDoc , appSep $ docForceSingleline thenExprDoc , appSep $ elseDoc , docForceSingleline elseExprDoc ] -- either -- if expr -- then foo -- bar -- else foo -- bar -- or -- if expr -- then -- stuff -- else -- stuff -- note that this has par-spacing addAlternative $ docSetParSpacing $ docAddBaseY BrIndentRegular $ docPar (docSeq [ -- TODO92 docNodeAnnKW lexpr Nothing $ appSep $ ifDoc , -- TODO92 docNodeAnnKW lexpr (Just AnnIf) $ docForceSingleline ifExprDoc ] ) (docLines [ docAddBaseY BrIndentRegular -- TODO92 $ docNodeAnnKW lexpr (Just AnnThen) $ docNonBottomSpacing $ docAlt [ docSeq [ appSep $ thenDoc , docForceParSpacing thenExprDoc ] , docAddBaseY BrIndentRegular $ docPar (thenDoc) thenExprDoc ] , docAddBaseY BrIndentRegular $ docNonBottomSpacing $ docAlt [ docSeq [ appSep $ elseDoc , docForceParSpacing elseExprDoc ] , docAddBaseY BrIndentRegular $ docPar elseDoc elseExprDoc ] ] ) -- either -- if multi -- line -- condition -- then foo -- bar -- else foo -- bar -- or -- if multi -- line -- condition -- then -- stuff -- else -- stuff -- note that this does _not_ have par-spacing addAlternative $ docAddBaseY BrIndentRegular $ docPar (docAddBaseY maySpecialIndent $ docSeq [ -- TODO92 docNodeAnnKW lexpr Nothing $ appSep $ ifDoc , -- TODO92 docNodeAnnKW lexpr (Just AnnIf) $ ifExprDoc ] ) (docLines [ docAddBaseY BrIndentRegular -- TODO92 $ docNodeAnnKW lexpr (Just AnnThen) $ docAlt [ docSeq [ appSep $ thenDoc , docForceParSpacing thenExprDoc ] , docAddBaseY BrIndentRegular $ docPar (thenDoc) thenExprDoc ] , docAddBaseY BrIndentRegular $ docAlt [ docSeq [ appSep $ elseDoc , docForceParSpacing elseExprDoc ] , docAddBaseY BrIndentRegular $ docPar elseDoc elseExprDoc ] ] ) addAlternative $ docSetBaseY $ docLines [ docAddBaseY maySpecialIndent $ docSeq [ -- TODO92 docNodeAnnKW lexpr Nothing $ appSep $ ifDoc , -- TODO92 docNodeAnnKW lexpr (Just AnnIf) $ ifExprDoc ] , -- TODO92 docNodeAnnKW lexpr (Just AnnThen) $ docAddBaseY BrIndentRegular $ docPar (thenDoc) thenExprDoc , docAddBaseY BrIndentRegular $ docPar elseDoc elseExprDoc ] HsMultiIf epAnn cases -> do binderDoc <- docLit $ Text.pack "->" let hasComments = hasAnyCommentsBelow epAnn || any (\(L _ (GRHS gEpAnn _ _)) -> hasAnyCommentsBelow gEpAnn) cases let posIf = obtainAnnPos epAnn AnnIf docSetParSpacing $ docAddBaseY BrIndentRegular $ docPar (docHandleComms posIf $ docLit $ Text.pack "if") (layoutPatternBindFinal Nothing binderDoc Nothing (Right cases) Nothing hasComments ) HsLet epAnn binds exp1 -> docHandleComms epAnn $ do let AnnsLet spanLet spanIn = anns epAnn let hasComments = hasAnyCommentsBelow lexpr let wrapLet = docHandleComms spanLet let wrapIn = docHandleComms spanIn mBindDocs <- layoutLocalBinds binds let ifIndentFreeElse :: a -> a -> a ifIndentFreeElse x y = case indentPolicy of IndentPolicyLeft -> y IndentPolicyMultiple -> y IndentPolicyFree -> x expDoc1 <- shareDoc $ layoutExpr exp1 -- this `docSetBaseAndIndent` might seem out of place (especially the -- Indent part; setBase is necessary due to the use of docLines below), -- but is here due to ghc-exactprint's DP handling of "let" in -- particular. -- Just pushing another indentation level is a straightforward approach -- to making brittany idempotent, even though the result is non-optimal -- if "let" is moved horizontally as part of the transformation, as the -- comments before the first let item are moved horizontally with it. letDoc <- shareDoc $ wrapLet $ docLit $ Text.pack "let" inDoc <- shareDoc $ wrapIn $ docLit $ Text.pack "in" docSetBaseAndIndent $ case fmap snd mBindDocs of Just [bindDoc] -> runFilteredAlternative $ do addAlternativeCond (not hasComments) $ docSeq [ appSep $ letDoc , appSep $ docForceSingleline (pure bindDoc) , appSep $ inDoc , docForceSingleline expDoc1 ] addAlternative $ docLines [ docAlt [ docSeq [ appSep $ letDoc , ifIndentFreeElse docSetBaseAndIndent docForceSingleline $ pure bindDoc ] , docAddBaseY BrIndentRegular $ docPar (letDoc) (docSetBaseAndIndent $ pure bindDoc) ] , docAlt [ docSeq [ appSep $ wrapIn $ docLit $ Text.pack $ ifIndentFreeElse "in " "in" , ifIndentFreeElse docSetBaseAndIndent docForceSingleline expDoc1 ] , docAddBaseY BrIndentRegular $ docPar (inDoc) (docSetBaseY expDoc1) ] ] Just bindDocs@(_ : _) -> runFilteredAlternative $ do --either -- let -- a = b -- c = d -- in foo -- bar -- baz --or -- let -- a = b -- c = d -- in -- fooooooooooooooooooo let noHangingBinds = [ docNonBottomSpacing $ docAddBaseY BrIndentRegular $ docPar (letDoc) (docSetBaseAndIndent $ docLines $ pure <$> bindDocs) , docSeq [ wrapIn $ docLit $ Text.pack "in " , docAddBaseY BrIndentRegular $ docForceParSpacing expDoc1 ] ] addAlternative $ case indentPolicy of IndentPolicyLeft -> docLines noHangingBinds IndentPolicyMultiple -> docLines noHangingBinds IndentPolicyFree -> docLines [ -- TODO92 docNodeAnnKW lexpr (Just AnnLet) $ docSeq [ appSep $ letDoc , docSetBaseAndIndent $ docLines $ pure <$> bindDocs ] , docSeq [appSep $ wrapIn $ docLit $ Text.pack "in ", docSetBaseY expDoc1] ] addAlternative $ docLines [ -- TODO92 docNodeAnnKW lexpr (Just AnnLet) $ docAddBaseY BrIndentRegular $ docPar (letDoc) (docSetBaseAndIndent $ docLines $ pure <$> bindDocs) , docAddBaseY BrIndentRegular $ docPar (inDoc) (docSetBaseY $ expDoc1) ] _ -> docSeq [ docForceSingleline $ docSeq [ letDoc , docSeparator , inDoc ] , docSeparator , expDoc1 ] -- docSeq [appSep $ docLit "let in", expDoc1] HsDo epAnn stmtCtx (L (SrcSpanAnn stmtEpAnn _) stmts) -> docHandleComms epAnn $ do case stmtCtx of DoExpr _ -> do stmtDocs <- docHandleComms stmtEpAnn $ do stmts `forM` docHandleListElemComms layoutStmt docSetParSpacing $ docAddBaseY BrIndentRegular $ docPar (docLit $ Text.pack "do") (docSetBaseAndIndent $ docNonBottomSpacing $ docLines $ pure <$> stmtDocs ) MDoExpr _ -> do stmtDocs <- docHandleComms stmtEpAnn $ do stmts `forM` docHandleListElemComms layoutStmt docSetParSpacing $ docAddBaseY BrIndentRegular $ docPar (docLit $ Text.pack "mdo") (docSetBaseAndIndent $ docNonBottomSpacing $ docLines $ pure <$> stmtDocs ) x | case x of ListComp -> True MonadComp -> True _ -> False -> do stmtDocs <- docHandleComms stmtEpAnn $ stmts `forM` docHandleListElemComms layoutStmt let hasComments = hasAnyCommentsBelow lexpr runFilteredAlternative $ do addAlternativeCond (not hasComments) $ docSeq [ -- TODO92 docNodeAnnKW lexpr Nothing $ appSep $ docLit $ Text.pack "[" , -- TODO92 docNodeAnnKW lexpr (Just AnnOpenS) $ appSep $ docForceSingleline $ pure (List.last stmtDocs) , appSep $ docLit $ Text.pack "|" , docSeq $ List.intersperse docCommaSep $ (docForceSingleline . pure) <$> List.init stmtDocs , docLit $ Text.pack " ]" ] addAlternative $ let start = docCols ColListComp [ -- TODO92 docNodeAnnKW lexpr Nothing $ appSep $ docLit $ Text.pack "[" , docSetBaseY -- TODO92 $ docNodeAnnKW lexpr (Just AnnOpenS) $ pure (List.last stmtDocs) ] (s1, sM) = case List.init stmtDocs of (a: b) -> (a, b) _ -> error "layoutExp: stmtDocs list too short" line1 = docCols ColListComp [appSep $ docLit $ Text.pack "|", pure s1] lineM = sM <&> \d -> docCols ColListComp [docCommaSep, pure d] end = docLit $ Text.pack "]" in docSetBaseY $ docLines $ [start, line1] ++ lineM ++ [end] _ -> do -- TODO unknownNodeError "HsDo{} unknown stmtCtx" lexpr ExplicitList listEpAnn elems@(_ : _) -> docHandleComms listEpAnn $ do let posOpen = obtainAnnPos listEpAnn AnnOpenS let posClose = obtainAnnPos listEpAnn AnnCloseS let openDoc = docHandleComms posOpen $ docLitS "[" let closeDoc = docHandleComms posClose $ docLitS "]" elemDocs <- elems `forM` (shareDoc . docHandleListElemComms layoutExpr) let hasComments = hasAnyCommentsBelow lexpr case splitFirstLast elemDocs of FirstLastEmpty -> docSeq [ docLit $ Text.pack "[" , closeDoc ] FirstLastSingleton e -> docAlt [ docSeq [ openDoc , docForceSingleline e , closeDoc ] , docSetBaseY $ docLines [ docSeq [ openDoc , docSeparator , docSetBaseY $ e ] , closeDoc ] ] FirstLast e1 ems eN -> runFilteredAlternative $ do addAlternativeCond (not hasComments) $ docSeq $ [openDoc] ++ List.intersperse docCommaSep (docForceSingleline <$> (e1 : ems ++ [eN]) ) ++ [closeDoc] addAlternative $ let start = docCols ColList [appSep $ openDoc, e1] linesM = ems <&> \d -> docCols ColList [docCommaSep, d] lineN = docCols ColList [docCommaSep, eN] in docSetBaseY $ docLines $ [start] ++ linesM ++ [lineN] ++ [closeDoc] ExplicitList epAnn [] -> docHandleComms epAnn $ docLit $ Text.pack "[]" RecordCon epAnn lname fields -> docHandleComms epAnn $ do let (wrapOpen, wrapClose) = case epAnn of EpAnn _ [open, close] _ -> ( docHandleComms (obtainAnnPos open AnnOpenC) , docHandleComms (obtainAnnPos close AnnCloseC) ) _ -> (id, id) fieldLayouter = \case FieldOcc _ lnameF -> docLit (lrdrNameToText lnameF) XFieldOcc _ -> error "XFieldOcc" case fields of HsRecFields fs Nothing -> do let nameDoc = docHandleComms lname $ docLit $ lrdrNameToText lname recordExpression False wrapOpen id wrapClose indentPolicy lexpr nameDoc fieldLayouter fs HsRecFields [] (Just (L dotdotLoc 0)) -> do let wrapDotDot = docHandleComms dotdotLoc let t = lrdrNameToText lname docHandleComms lname $ docSeq [ docLit t , docSeparator , wrapOpen $ docLitS "{" , docSeparator , wrapDotDot $ docLitS ".." , docSeparator , wrapClose $ docLitS "}" ] HsRecFields fs@(_ : _) (Just (L dotdotLoc dotdoti)) | dotdoti == length fs -> do let nameDoc = docHandleComms lname $ docLit $ lrdrNameToText lname let wrapDotDot = docHandleComms dotdotLoc recordExpression True wrapOpen wrapDotDot wrapClose indentPolicy lexpr nameDoc fieldLayouter fs _ -> unknownNodeError "RecordCon with puns" lexpr RecordUpd epAnn rExpr (Left fields) -> docHandleComms epAnn $ do let (wrapOpen, wrapClose) = case epAnn of EpAnn _ [open, close] _ -> ( docHandleComms $ obtainAnnPos open AnnOpenC , docHandleComms $ obtainAnnPos close AnnCloseC ) _ -> (id, id) let fieldLayouter = \case Unambiguous _ n -> docLit (lrdrNameToText n) Ambiguous _ n -> docLit (lrdrNameToText n) XAmbiguousFieldOcc _ -> error "XAmbiguousFieldOcc" rExprDoc <- shareDoc $ layoutExpr rExpr recordExpression False wrapOpen id wrapClose indentPolicy lexpr rExprDoc fieldLayouter fields RecordUpd epAnn rExpr (Right fields) -> do let (wrapOpen, wrapClose) = case epAnn of EpAnn _ [open, close] _ -> ( docHandleComms $ obtainAnnPos open AnnOpenC , docHandleComms $ obtainAnnPos close AnnCloseC ) _ -> (id, id) rExprDoc <- shareDoc $ layoutExpr rExpr let labelLayouter label = case label of L flAnn (HsFieldLabel _ (L _ n)) -> docHandleComms flAnn $ docLitS $ FastString.unpackFS n L _ann (XHsFieldLabel _) -> error "XHsFieldLabel" let fieldLayouter = \case FieldLabelStrings [] -> docEmpty FieldLabelStrings [label] -> labelLayouter label FieldLabelStrings labels -> docSeq $ List.intersperse docCommaSep $ map labelLayouter labels recordExpression False wrapOpen id wrapClose indentPolicy lexpr rExprDoc fieldLayouter fields ExprWithTySig _ exp1 (HsWC _ typ1) -> do expDoc <- shareDoc $ layoutExpr exp1 typDoc <- shareDoc $ layoutSigType typ1 docSeq [appSep expDoc, appSep $ docLit $ Text.pack "::", typDoc] ArithSeq _ Nothing info -> case info of From e1 -> do e1Doc <- shareDoc $ layoutExpr e1 docSeq [ docLit $ Text.pack "[" , appSep $ docForceSingleline e1Doc , docLit $ Text.pack "..]" ] FromThen e1 e2 -> do e1Doc <- shareDoc $ layoutExpr e1 e2Doc <- shareDoc $ layoutExpr e2 docSeq [ docLit $ Text.pack "[" , docForceSingleline e1Doc , appSep $ docLit $ Text.pack "," , appSep $ docForceSingleline e2Doc , docLit $ Text.pack "..]" ] FromTo e1 eN -> do e1Doc <- shareDoc $ layoutExpr e1 eNDoc <- shareDoc $ layoutExpr eN docSeq [ docLit $ Text.pack "[" , appSep $ docForceSingleline e1Doc , appSep $ docLit $ Text.pack ".." , docForceSingleline eNDoc , docLit $ Text.pack "]" ] FromThenTo e1 e2 eN -> do e1Doc <- shareDoc $ layoutExpr e1 e2Doc <- shareDoc $ layoutExpr e2 eNDoc <- shareDoc $ layoutExpr eN docSeq [ docLit $ Text.pack "[" , docForceSingleline e1Doc , appSep $ docLit $ Text.pack "," , appSep $ docForceSingleline e2Doc , appSep $ docLit $ Text.pack ".." , docForceSingleline eNDoc , docLit $ Text.pack "]" ] HsGetField _epAnn _exp1 _field -> do let labelLayouter label = case label of L flAnn (HsFieldLabel _ (L _ n)) -> docHandleComms flAnn $ docLitS $ FastString.unpackFS n L _ann (XHsFieldLabel _) -> error "XHsFieldLabel" gather :: [ToBriDocM BriDocNumbered] -> LHsExpr GhcPs -> (LHsExpr GhcPs, [ToBriDocM BriDocNumbered]) gather list = \case L _ (HsGetField epAnn l r) -> gather (docHandleComms epAnn $ labelLayouter r : list) l x -> (x, list) let (headE, paramEs) = gather [] lexpr expDoc <- shareDoc $ layoutExpr headE -- this only has single-line layout, afaik docForceSingleline $ docSeq $ intersperse (docLitS ".") (expDoc: paramEs) HsProjection epAnn (f1 :| fR) -> do let labelLayouter label = case label of L flAnn (HsFieldLabel _ (L _ n)) -> docHandleComms flAnn $ docLitS $ FastString.unpackFS n L _ann (XHsFieldLabel _) -> error "XHsFieldLabel" docForceSingleline $ docHandleComms epAnn $ docSeq ( [ docLitS "(" ] ++ [ doc | f <- f1:fR, doc <- [docLitS ".", labelLayouter f] ] ++ [ docLitS ")" ] ) ArithSeq{} -> briDocByExactInlineOnly "ArithSeq" lexpr HsBracket{} -> do -- TODO briDocByExactInlineOnly "HsBracket{}" lexpr HsRnBracketOut{} -> do -- TODO briDocByExactInlineOnly "HsRnBracketOut{}" lexpr HsTcBracketOut{} -> do -- TODO briDocByExactInlineOnly "HsTcBracketOut{}" lexpr HsSpliceE _ (HsQuasiQuote _ _ quoter _loc content) -> do allocateNode $ BDPlain (Text.pack $ "[" ++ showOutputable quoter ++ "|" ++ showOutputable content ++ "|]" ) HsSpliceE{} -> do -- TODO briDocByExactInlineOnly "HsSpliceE{}" lexpr HsProc{} -> do -- TODO briDocByExactInlineOnly "HsProc{}" lexpr HsStatic{} -> do -- TODO briDocByExactInlineOnly "HsStatic{}" lexpr HsTick{} -> do -- TODO briDocByExactInlineOnly "HsTick{}" lexpr HsBinTick{} -> do -- TODO briDocByExactInlineOnly "HsBinTick{}" lexpr HsConLikeOut{} -> do -- TODO briDocByExactInlineOnly "HsWrap{}" lexpr ExplicitSum{} -> do -- TODO briDocByExactInlineOnly "ExplicitSum{}" lexpr HsPragE{} -> do -- TODO briDocByExactInlineOnly "HsPragE{}" lexpr recordExpression :: Bool -> (ToBriDocM BriDocNumbered -> ToBriDocM BriDocNumbered) -> (ToBriDocM BriDocNumbered -> ToBriDocM BriDocNumbered) -> (ToBriDocM BriDocNumbered -> ToBriDocM BriDocNumbered) -> IndentPolicy -> LocatedA lExpr -> ToBriDocM BriDocNumbered -> (field -> ToBriDocM BriDocNumbered) -- -> [LHsFieldBind GhcPs (LFieldOcc p) (LHsExpr GhcPs)] -> [LHsRecField' GhcPs field (LHsExpr GhcPs)] -> ToBriDocM BriDocNumbered recordExpression False wrapO _wrapDD wrapC _ _lexpr nameDoc _ [] = docSeq [ -- TODO92 docNodeAnnKW lexpr (Just AnnOpenC) $ docSeq [nameDoc, wrapO $ docLit $ Text.pack "{"] , wrapC $ docLit $ Text.pack "}" ] recordExpression True wrapO wrapDD wrapC _ _lexpr nameDoc _ [] = docSeq -- this case might still be incomplete, and is probably not used -- atm anyway. [ nameDoc , wrapO $ docLit $ Text.pack "{" , docSeparator , wrapDD $ docLitS ".." , docSeparator , wrapC $ docLit $ Text.pack "}" ] recordExpression dotdot wrapO wrapDD wrapC indentPolicy _lexpr nameDoc nameLayouter (rF1 : rFr) = do let mkFieldTuple = \case L srcSpan (HsRecField fEpAnn (L _ nameThing) rFExpr pun) -> do let (posStart, _posSomething) = case fEpAnn of -- TODO92 not sure what posSomething is EpAnn anch [AddEpAnn _ span] _ -> ( Just $ GHC.realSrcSpanStart $ anchor anch , Just $ epaLocationRealSrcSpanStart span ) _ -> (Nothing, Nothing) let posComma = case srcSpan of SrcSpanAnn (EpAnn _ (AnnListItem items) _) _ -> case items of [AddCommaAnn span] -> Just $ epaLocationRealSrcSpanStart span _ -> Nothing SrcSpanAnn EpAnnNotUsed _ -> Nothing fnameDoc <- shareDoc $ nameLayouter nameThing if pun then pure $ Left (posStart, fnameDoc) else do expDoc <- shareDoc $ docFlushCommsPost posComma $ layoutExpr rFExpr pure $ Right (posStart, fnameDoc, expDoc) fieldTuple1 <- mkFieldTuple rF1 fieldTupleR <- rFr `forM` mkFieldTuple let fieldWiths :: a -> a -> ( a -> Either (Maybe GHC.RealSrcLoc, ToBriDocM BriDocNumbered) ( Maybe GHC.RealSrcLoc , ToBriDocM BriDocNumbered , ToBriDocM BriDocNumbered ) -> ToBriDocM BriDocNumbered ) -> [ToBriDocM BriDocNumbered] fieldWiths extra1 extraR f = f extra1 fieldTuple1 : map (f extraR) fieldTupleR runFilteredAlternative $ do -- container { fieldA = blub, fieldB = blub } addAlternative $ docSeq [ -- TODO92 docNodeAnnKW lexpr Nothing $ appSep $ docForceSingleline nameDoc , appSep $ wrapO $ docLit $ Text.pack "{" , docSeq $ List.intersperse docCommaSep $ fieldWiths () () $ \() -> \case Left (pos, fnameDoc) -> docHandleComms pos $ fnameDoc Right (pos, fnameDoc, expDoc) -> docHandleComms pos $ docSeq [ appSep $ fnameDoc , appSep $ docLit $ Text.pack "=" , docForceSingleline $ expDoc ] , if dotdot then docSeq [docCommaSep, wrapDD $ docLit $ Text.pack "..", docSeparator] else docSeparator , wrapC $ docLit $ Text.pack "}" ] -- hanging single-line fields -- container { fieldA = blub -- , fieldB = blub -- } addAlternativeCond (indentPolicy == IndentPolicyFree) $ docSeq [ -- TODO92 docNodeAnnKW lexpr Nothing $ docForceSingleline $ appSep nameDoc , docSetBaseY $ docLines $ let fieldLines = fieldWiths (appSep $ wrapO $ docLit $ Text.pack "{") docCommaSep $ \prep -> \case Left (pos, fnameDoc) -> docCols ColRec [ prep , docHandleComms pos $ fnameDoc ] Right (pos, fnameDoc, expDoc) -> docCols ColRec [ prep , docHandleComms pos $ appSep $ fnameDoc , docSeq [appSep $ docLit $ Text.pack "=", docForceSingleline expDoc] ] dotdotLine = if dotdot then docCols ColRec [ -- TODO92 docNodeAnnKW lexpr (Just AnnOpenC) docCommaSep , wrapDD $ docLit $ Text.pack ".." ] else -- TODO92 docNodeAnnKW lexpr (Just AnnOpenC) docEmpty lineN = wrapC $ docLit $ Text.pack "}" in fieldLines ++ [dotdotLine, lineN] ] -- non-hanging with expressions placed to the right of the names -- container -- { fieldA = blub -- , fieldB = potentially -- multiline -- } addAlternative $ docSetParSpacing $ docAddBaseY BrIndentRegular $ docPar (-- TODO92 docNodeAnnKW lexpr Nothing nameDoc) (docNonBottomSpacing $ docLines $ let fieldLines = fieldWiths (appSep $ wrapO $ docLit $ Text.pack "{") docCommaSep $ \prep -> \case Left (pos, fnameDoc) -> docCols ColRec [ prep , docHandleComms pos $ fnameDoc ] Right (pos, fnameDoc, expDoc) -> docCols ColRec [ prep , docHandleComms pos $ appSep $ fnameDoc , runFilteredAlternative $ do addAlternativeCond (indentPolicy == IndentPolicyFree) $ do docSeq [appSep $ docLit $ Text.pack "=", docSetBaseY expDoc] addAlternative $ do docSeq [appSep $ docLit $ Text.pack "=", docForceParSpacing expDoc] addAlternative $ do docAddBaseY BrIndentRegular $ docPar (docLit $ Text.pack "=") expDoc ] dotdotLine = if dotdot then docCols ColRec [ -- TODO92 docNodeAnnKW lexpr (Just AnnOpenC) docCommaSep , wrapDD $ docLit $ Text.pack ".." ] else -- TODO92 docNodeAnnKW lexpr (Just AnnOpenC) docEmpty lineN = wrapC $ docLit $ Text.pack "}" in fieldLines ++ [dotdotLine, lineN] ) litBriDoc :: HsLit GhcPs -> BriDocWrapped litBriDoc = \case HsChar (SourceText t) _c -> BDLit $ Text.pack t -- BDLit $ Text.pack $ ['\'', c, '\''] HsCharPrim (SourceText t) _c -> BDLit $ Text.pack t -- BDLit $ Text.pack $ ['\'', c, '\''] HsString (SourceText t) _fastString -> BDLit $ Text.pack t -- BDLit $ Text.pack $ FastString.unpackFS fastString HsStringPrim (SourceText t) _byteString -> BDLit $ Text.pack t -- BDLit $ Text.pack $ Data.ByteString.Char8.unpack byteString HsInt _ (IL (SourceText t) _ _) -> BDLit $ Text.pack t -- BDLit $ Text.pack $ show i HsIntPrim (SourceText t) _i -> BDLit $ Text.pack t -- BDLit $ Text.pack $ show i HsWordPrim (SourceText t) _i -> BDLit $ Text.pack t -- BDLit $ Text.pack $ show i HsInt64Prim (SourceText t) _i -> BDLit $ Text.pack t -- BDLit $ Text.pack $ show i HsWord64Prim (SourceText t) _i -> BDLit $ Text.pack t -- BDLit $ Text.pack $ show i HsInteger (SourceText t) _i _type -> BDLit $ Text.pack t -- BDLit $ Text.pack $ show i HsRat _ (FL (SourceText t) _ _ _ _) _type -> BDLit $ Text.pack t HsFloatPrim _ (FL (SourceText t) _ _ _ _) -> BDLit $ Text.pack t HsDoublePrim _ (FL (SourceText t) _ _ _ _) -> BDLit $ Text.pack t _ -> error "litBriDoc: literal with no SourceText" overLitValBriDoc :: OverLitVal -> BriDocWrapped overLitValBriDoc = \case HsIntegral (IL (SourceText t) _ _) -> BDLit $ Text.pack t HsFractional (FL (SourceText t) _ _ _ _) -> BDLit $ Text.pack t HsIsString (SourceText t) _ -> BDLit $ Text.pack t _ -> error "overLitValBriDoc: literal with no SourceText"