fay-0.24.2.0: src/Fay/Types/Printer.hs
{-# LANGUAGE FlexibleContexts #-}
module Fay.Types.Printer
( PrintReader(..)
, defaultPrintReader
, PrintWriter(..)
, pwOutputString
, PrintState(..)
, defaultPrintState
, Printer(..)
, Printable(..)
, execPrinter
, indented
, newline
, write
, askIf
, mapping
) where
import Fay.Compiler.Prelude
import Control.Monad.RWS (RWS, asks, execRWS, get, modify, put, tell)
import Data.List (elemIndex)
import Data.Maybe (fromMaybe)
import Data.String
import Language.Haskell.Exts
import SourceMap.Types
import qualified Data.Semigroup as SG
-- | Global options of the printer
data PrintReader = PrintReader
{ prPretty :: Bool -- ^ Are we to pretty print?
, prPrettyThunks :: Bool -- ^ Use pretty thunk names?
, prPrettyOperators :: Bool -- ^ Use pretty operators?
}
-- | default printer options (non-pretty printing)
defaultPrintReader :: PrintReader
defaultPrintReader = PrintReader False False False
-- | Output of printer
data PrintWriter = PrintWriter
{ pwMappings :: [Mapping] -- ^ Source mappings.
, pwOutput :: ShowS -- ^ The current output.
}
pwOutputString :: PrintWriter -> String
pwOutputString (PrintWriter _ out) = out ""
instance SG.Semigroup PrintWriter where
(PrintWriter a b) <> (PrintWriter x y) = PrintWriter (a ++ x) (b . y)
-- | Output concatenation
instance Monoid PrintWriter where
mempty = PrintWriter [] id
mappend = (<>)
-- | The state of the pretty printer.
data PrintState = PrintState
{ psLine :: Int -- ^ The current line.
, psColumn :: Int -- ^ Current column.
, psIndentLevel :: Int -- ^ Current indentation level.
, psNewline :: Bool -- ^ Just outputted a newline?
}
-- | Default state.
defaultPrintState :: PrintState
defaultPrintState = PrintState 0 0 0 False
-- | The printer.
newtype Printer = Printer
{ runPrinter :: RWS PrintReader PrintWriter PrintState () }
execPrinter :: Printer -> PrintReader -> PrintWriter
execPrinter (Printer p) r = snd $ execRWS p r defaultPrintState
instance SG.Semigroup Printer where
(Printer p) <> (Printer q) = Printer (p >> q)
instance Monoid Printer where
mempty = Printer $ return ()
mappend = (<>)
-- | Print some value.
class Printable a where
printJS :: a -> Printer
-- | Print the given printer indented.
indented :: Printer -> Printer
indented (Printer p) = Printer $ asks prPretty >>= \pretty ->
when pretty (addToIndentLevel 1) >> p >> when pretty (addToIndentLevel (-1))
where addToIndentLevel d = modify (\ps -> ps { psIndentLevel = psIndentLevel ps + d })
-- | Output a newline and makes next line indented when prPretty is True.
-- Does nothing when prPretty is False
newline :: Printer
newline = Printer $ asks prPretty >>= flip when writeNewline
where writeNewline = writeRWS "\n" >> modify (\s -> s { psNewline = True })
-- | Write out a raw string, respecting the indentation
-- Note: if you pass a string with newline characters, it will print them
-- out even if prPretty is set to False. Also next line won't be indented.
-- If you want write a smart newline (that is the one which will be written
-- out only if prPretty is true, and after which the line will be indented)
-- use `newline`)
write :: String -> Printer
write = Printer . writeRWS
writeRWS :: String -> RWS PrintReader PrintWriter PrintState ()
writeRWS x = do
ps <- get
let out = if psNewline ps
then replicate (2 * psIndentLevel ps) ' ' ++ x
else x
tell mempty { pwOutput = (out++) }
let newLines = length (filter (== '\n') x)
put ps { psLine = psLine ps + newLines
, psColumn = fromMaybe (psColumn ps + length x) . elemIndex '\n' $ reverse x
, psNewline = False
}
-- | Write out a string, updating the current position information.
instance IsString Printer where
fromString = write
-- | exec one of Printers depending on PrintReader property.
askIf :: (PrintReader -> Bool) -> Printer -> Printer -> Printer
askIf f (Printer p) (Printer q) = Printer $ asks f >>= (\b -> if b then p else q)
-- | Generate a mapping from the Haskell location to the current point in the output.
mapping :: SrcSpan -> Printer
mapping srcSpan = Printer $ get >>= \ps ->
let m = Mapping { mapGenerated = Pos (fromIntegral (psLine ps))
(fromIntegral (psColumn ps))
, mapOriginal = Just (Pos (fromIntegral (srcSpanStartLine srcSpan))
(fromIntegral (srcSpanStartColumn srcSpan) - 1))
, mapSourceFile = Just (srcSpanFilename srcSpan)
, mapName = Nothing
}
in tell $ mempty { pwMappings = [m] }