{-# LANGUAGE OverloadedStrings #-}
module Floskell.Printers
( getConfig
, getOption
, cut
, oneline
, ignoreOneline
, write
, string
, int
, space
, newline
, blankline
, spaceOrNewline
, withTabStops
, atTabStop
, mayM_
, withPrefix
, withPostfix
, withIndentConfig
, withIndent
, withIndentFlex
, withIndentAfter
, withIndentBy
, withLayout
, inter
, getNextColumn
, column
, aligned
, indented
, indentedBy
, onside
, depend
, depend'
, parens
, brackets
, group
, groupH
, groupV
, operator
, operatorH
, operatorV
, alignOnOperator
, alignOnOperatorH
, alignOnOperatorV
, withOperatorFormatting
, withOperatorFormattingH
, withOperatorFormattingV
, operatorSectionL
, operatorSectionR
, comma
) where
import Control.Applicative ( (<|>) )
import Control.Monad ( guard, unless, when )
import Control.Monad.Search ( cost, winner )
import Control.Monad.State.Strict ( get, gets, modify )
import Data.ByteString ( ByteString )
import qualified Data.ByteString as BS
import qualified Data.ByteString.Builder as BB
import qualified Data.ByteString.Lazy as BL
import Data.List ( intersperse )
import qualified Data.Map.Strict as Map
import Data.Monoid ( (<>) )
import qualified Floskell.Buffer as Buffer
import Floskell.Config
import Floskell.Types
getConfig :: (Config -> b) -> Printer b
getConfig f = f <$> gets psConfig
getOption :: (OptionConfig -> a) -> Printer a
getOption f = getConfig (f . cfgOptions)
linePenalty :: Bool -> Int -> Printer Penalty
linePenalty eol col = do
indentLevel <- gets psIndentLevel
config <- getConfig cfgPenalty
let maxcol = penaltyMaxLineLength config
let pLinebreak = onlyIf eol $ penaltyLinebreak config
let pIndent = indentLevel * penaltyIndent config
let pOverfull = onlyIf (col > maxcol) $ penaltyOverfull config
* (col - maxcol) + penaltyOverfullOnce config
return . Penalty $ pLinebreak + pIndent + pOverfull
where
onlyIf cond penalty = if cond then penalty else 0
cut :: Printer a -> Printer a
cut = winner
closeEolComment :: Printer ()
closeEolComment = do
eol <- gets psEolComment
when eol newline
withOutputRestriction :: OutputRestriction -> Printer a -> Printer a
withOutputRestriction r p = do
orig <- gets psOutputRestriction
modify $ \s -> s { psOutputRestriction = r }
result <- p
modify $ \s -> s { psOutputRestriction = orig }
return result
oneline :: Printer a -> Printer a
oneline p = do
closeEolComment
withOutputRestriction NoOverflowOrLinebreak p
ignoreOneline :: Printer a -> Printer a
ignoreOneline = withOutputRestriction Anything
write :: ByteString -> Printer ()
write x = do
closeEolComment
write' x
where
write' x' = do
state <- get
let indentLevel = psIndentLevel state
out = if psNewline state
then BS.replicate indentLevel 32 <> x'
else x'
buffer = psBuffer state
newCol = Buffer.column buffer + BS.length out
guard $ psOutputRestriction state == Anything || newCol
< penaltyMaxLineLength (cfgPenalty (psConfig state))
penalty <- linePenalty False newCol
when (penalty /= mempty) $ cost mempty penalty
modify (\s ->
s { psBuffer = Buffer.write out buffer, psEolComment = False })
string :: String -> Printer ()
string = write . BL.toStrict . BB.toLazyByteString . BB.stringUtf8
int :: Int -> Printer ()
int = string . show
space :: Printer ()
space = do
comment <- gets psEolComment
unless comment $ write " "
newline :: Printer ()
newline = do
modify (\s ->
s { psIndentLevel = psIndentLevel s + psOnside s, psOnside = 0 })
state <- get
guard $ psOutputRestriction state /= NoOverflowOrLinebreak
penalty <- linePenalty True (psColumn state)
when (penalty /= mempty) $ cost penalty mempty
modify (\s -> s { psBuffer = Buffer.newline (psBuffer state)
, psEolComment = False
})
blankline :: Printer ()
blankline = newline >> newline
spaceOrNewline :: Printer ()
spaceOrNewline = space <|> newline
withTabStops :: [(TabStop, Maybe Int)] -> Printer a -> Printer a
withTabStops stops p = do
col <- getNextColumn
oldstops <- gets psTabStops
modify $ \s ->
s { psTabStops =
foldr (\(k, v) -> Map.alter (const $ fmap (\x -> col + x) v) k)
(psTabStops s)
stops
}
res <- p
modify $ \s -> s { psTabStops = oldstops }
return res
atTabStop :: TabStop -> Printer ()
atTabStop tabstop = do
mstop <- gets (Map.lookup tabstop . psTabStops)
mayM_ mstop $ \stop -> do
col <- getNextColumn
let padding = max 0 (stop - col)
write (BS.replicate padding 32)
mayM_ :: Maybe a -> (a -> Printer ()) -> Printer ()
mayM_ Nothing _ = return ()
mayM_ (Just x) p = p x
withPrefix :: Applicative f => f a -> (x -> f b) -> x -> f b
withPrefix pre f x = pre *> f x
withPostfix :: Applicative f => f a -> (x -> f b) -> x -> f b
withPostfix post f x = f x <* post
withIndentConfig
:: (IndentConfig -> Indent) -> Printer a -> (Int -> Printer a) -> Printer a
withIndentConfig fn align indentby = do
cfg <- getConfig (fn . cfgIndent)
case cfg of
Align -> align
IndentBy i -> indentby i
AlignOrIndentBy i -> align <|> indentby i
withIndent :: (IndentConfig -> Indent) -> Printer a -> Printer a
withIndent fn p = withIndentConfig fn align indentby
where
align = do
space
aligned p
indentby i = indentedBy i $ do
newline
p
withIndentFlex :: (IndentConfig -> Indent) -> Printer a -> Printer a
withIndentFlex fn p = withIndentConfig fn align indentby
where
align = do
space
aligned p
indentby i = indentedBy i $ do
spaceOrNewline
p
withIndentAfter
:: (IndentConfig -> Indent) -> Printer () -> Printer a -> Printer a
withIndentAfter fn before p = withIndentConfig fn align indentby
where
align = aligned $ do
withIndentation id before
p
indentby i = do
withIndentation id before
indentedBy i p
withIndentBy :: (IndentConfig -> Int) -> Printer a -> Printer a
withIndentBy fn = withIndent (IndentBy . fn)
withLayout :: (LayoutConfig -> Layout) -> Printer a -> Printer a -> Printer a
withLayout fn flex vertical = do
cfg <- getConfig (fn . cfgLayout)
case cfg of
Flex -> flex
Vertical -> vertical
TryOneline -> oneline flex <|> vertical
inter :: Printer () -> [Printer ()] -> Printer ()
inter x = sequence_ . intersperse x
getNextColumn :: Printer Int
getNextColumn = do
st <- get
return $ if psEolComment st
then psIndentLevel st + psOnside st
else max (psColumn st) (psIndentLevel st)
withIndentation :: ((Int, Int) -> (Int, Int)) -> Printer a -> Printer a
withIndentation f p = do
prevIndent <- gets psIndentLevel
prevOnside <- gets psOnside
let (newIndent, newOnside) = f (prevIndent, prevOnside)
modify (\s -> s { psIndentLevel = newIndent, psOnside = newOnside })
r <- p
modify (\s -> s { psIndentLevel = prevIndent, psOnside = prevOnside })
return r
column :: Int -> Printer a -> Printer a
column i = withIndentation $ \(l, o) -> (i, if i > l then 0 else o)
aligned :: Printer a -> Printer a
aligned p = do
col <- getNextColumn
column col $ do
modify $ \s -> s { psOnside = 0 }
p
indented :: Printer a -> Printer a
indented p = do
i <- getConfig (cfgIndentOnside . cfgIndent)
indentedBy i p
indentedBy :: Int -> Printer a -> Printer a
indentedBy i p = do
level <- gets psIndentLevel
column (level + i) p
onside :: Printer a -> Printer a
onside p = do
closeEolComment
onsideIndent <- getConfig (cfgIndentOnside . cfgIndent)
withIndentation (\(l, _) -> (l, onsideIndent)) p
depend :: ByteString -> Printer a -> Printer a
depend kw = depend' (write kw)
depend' :: Printer () -> Printer a -> Printer a
depend' kw p = do
kw
space
indented p
parens :: Printer () -> Printer ()
parens p = do
write "("
aligned $ do
p
write ")"
brackets :: Printer () -> Printer ()
brackets p = do
write "["
aligned $ do
p
write "]"
group :: LayoutContext -> ByteString -> ByteString -> Printer () -> Printer ()
group ctx open close p = do
force <- getConfig (wsForceLinebreak . cfgGroupWs ctx open . cfgGroup)
if force then vert else oneline hor <|> vert
where
hor = groupH ctx open close p
vert = groupV ctx open close p
groupH :: LayoutContext -> ByteString -> ByteString -> Printer () -> Printer ()
groupH ctx open close p = do
ws <- getConfig (cfgGroupWs ctx open . cfgGroup)
write open
when (wsSpace Before ws) space
p
when (wsSpace After ws) space
write close
groupV :: LayoutContext -> ByteString -> ByteString -> Printer () -> Printer ()
groupV ctx open close p = aligned $ do
ws <- getConfig (cfgGroupWs ctx open . cfgGroup)
write open
if wsLinebreak Before ws then newline else when (wsSpace Before ws) space
p
if wsLinebreak After ws then newline else when (wsSpace After ws) space
write close
operator :: LayoutContext -> ByteString -> Printer ()
operator ctx op = withOperatorFormatting ctx op (write op) id
operatorH :: LayoutContext -> ByteString -> Printer ()
operatorH ctx op = withOperatorFormattingH ctx op (write op) id
operatorV :: LayoutContext -> ByteString -> Printer ()
operatorV ctx op = withOperatorFormattingV ctx op (write op) id
alignOnOperator :: LayoutContext -> ByteString -> Printer a -> Printer a
alignOnOperator ctx op p =
withOperatorFormatting ctx op (write op) (aligned . (*> p))
alignOnOperatorH :: LayoutContext -> ByteString -> Printer a -> Printer a
alignOnOperatorH ctx op p =
withOperatorFormattingH ctx op (write op) (aligned . (*> p))
alignOnOperatorV :: LayoutContext -> ByteString -> Printer a -> Printer a
alignOnOperatorV ctx op p =
withOperatorFormattingV ctx op (write op) (aligned . (*> p))
withOperatorFormatting :: LayoutContext
-> ByteString
-> Printer ()
-> (Printer () -> Printer a)
-> Printer a
withOperatorFormatting ctx op opp fn = do
force <- getConfig (wsForceLinebreak . cfgOpWs ctx op . cfgOp)
if force then vert else hor <|> vert
where
hor = withOperatorFormattingH ctx op opp fn
vert = withOperatorFormattingV ctx op opp fn
withOperatorFormattingH :: LayoutContext
-> ByteString
-> Printer ()
-> (Printer () -> Printer a)
-> Printer a
withOperatorFormattingH ctx op opp fn = do
ws <- getConfig (cfgOpWs ctx op . cfgOp)
when (wsSpace Before ws) space
fn $ do
opp
when (wsSpace After ws) space
withOperatorFormattingV :: LayoutContext
-> ByteString
-> Printer ()
-> (Printer () -> Printer a)
-> Printer a
withOperatorFormattingV ctx op opp fn = do
ws <- getConfig (cfgOpWs ctx op . cfgOp)
if wsLinebreak Before ws then newline else when (wsSpace Before ws) space
fn $ do
opp
if wsLinebreak After ws then newline else when (wsSpace After ws) space
operatorSectionL :: LayoutContext -> ByteString -> Printer () -> Printer ()
operatorSectionL ctx op opp = do
ws <- getConfig (cfgOpWs ctx op . cfgOp)
when (wsSpace Before ws) space
opp
operatorSectionR :: LayoutContext -> ByteString -> Printer () -> Printer ()
operatorSectionR ctx op opp = do
ws <- getConfig (cfgOpWs ctx op . cfgOp)
opp
when (wsSpace After ws) space
comma :: Printer ()
comma = operator Expression ","