packages feed

alms-0.6.0: src/Syntax/PprClass.hs

module Syntax.PprClass (
  -- * Documents
  Doc,
  -- * Pretty-printing class
  Ppr(..), IsInfix(..), ListStyle(..), listStyleBrack,
  -- ** Helpers
  ppr0, ppr1, pprPrec1, pprDepth,
  -- ** Context operations
  prec, prec0, mapPrec, prec1, descend, atPrec, atDepth,
  askPrec, ifPrec, askDepth, ifDepth,
  trimList, trimCat,
  -- *** For type name shortening
  TyNames(..), tyNames0,
  setTyNames, askTyNames, enterTyNames, lookupTyNames,
  -- * Pretty-printing combinators
  (>+>), (>?>), ifEmpty,
  vcat, sep, cat, fsep, fcat,
  -- * Renderers
  render, renderS, printDoc, printPpr, hPrintDoc, hPrintPpr,
  -- ** Instantiations of several context-sensitive functions
  --    with the zero context
  isEmpty, renderStyle, fullRender,
  -- ** Instance helpers
  showFromPpr, pprFromShow,
  -- * Alternate printing of 'Maybe'
  MAYBE(..),
  -- * Re-exports
  module Alt.PrettyPrint
) where

import Alt.PrettyPrint hiding ( Doc(..),
                                render, isEmpty, renderStyle, fullRender,
                                vcat, sep, cat, fsep, fcat )
import qualified Alt.PrettyPrint as P

import Data.Perhaps
import Syntax.Prec
import qualified Syntax.Strings as Strings
import AST.Ident (QTypId, ModId, Renamed)

import System.IO (Handle, stdout, hPutChar, hPutStr)
import qualified Data.Map as M
import qualified Data.Set as S

-- | Context for pretty-printing.
data PprContext
  = PprContext {
      pcPrec   :: !Int,
      pcDepth  :: !Int,
      pcTyName :: !TyNames
  }

data TyNames =
  TyNames {
    tnLookup   :: Int -> QTypId Renamed -> QTypId Renamed,
    tnEnter    :: ModId Renamed -> TyNames
  }

-- | Default context
pprContext0 :: PprContext
pprContext0  = PprContext {
  pcPrec   = 0,
  pcDepth  = -1,
  pcTyName = tyNames0
}

tyNames0 :: TyNames
tyNames0  = TyNames {
  tnLookup = const id,
  tnEnter  = const tyNames0
}

type Doc = P.Doc PprContext

data ListStyle 
  = ListStyle {
    listStyleBegin, listStyleEnd, listStylePunct :: Doc,
    listStyleDelimitEmpty, listStyleDelimitSingleton :: Bool,
    listStyleJoiner :: [Doc] -> Doc
  }

-- | Class for pretty-printing at different types
--
-- Minimal complete definition is one of:
--
-- * 'pprPrec'
--
-- * 'ppr'
class Ppr p where
  -- | Print current precedence
  ppr     :: p -> Doc
  -- | Print at the specified enclosing precedence
  pprPrec :: Int -> p -> Doc
  -- | Print a list in the default style
  pprList :: [p] -> Doc
  -- | Print a list in the specified style
  pprStyleList :: ListStyle -> [p] -> Doc
  -- | Style for printing lists
  listStyle   :: [p] -> ListStyle
  --
  --
  ppr         = asksD pcPrec . flip pprPrec
  pprPrec p   = prec p . ppr
  pprList xs  = pprStyleList (listStyle xs) xs
  --
  pprStyleList st [] =
    if listStyleDelimitEmpty st
      then listStyleBegin st <> listStyleEnd st
      else mempty
  pprStyleList st [x] =
    if listStyleDelimitSingleton st
      then listStyleBegin st <> ppr0 x <> listStyleEnd st
      else ppr x
  pprStyleList st xs  =
    listStyleBegin st <>
      listStyleJoiner st (punctuate (listStylePunct st) (map ppr0 xs))
    <> listStyleEnd st
  --
  listStyle _ = ListStyle {
    listStyleBegin            = lparen,
    listStyleEnd              = rparen,
    listStylePunct            = comma,
    listStyleDelimitEmpty     = False,
    listStyleDelimitSingleton = False,
    listStyleJoiner           = fsep
  }

-- | Style for printing square-bracketed lists.
listStyleBrack ∷ ListStyle
listStyleBrack = ListStyle {
  listStyleBegin            = lbrack,
  listStyleEnd              = rbrack,
  listStylePunct            = comma,
  listStyleDelimitEmpty     = True,
  listStyleDelimitSingleton = True,
  listStyleJoiner           = fsep
}

-- | Print at top level.
ppr0      :: Ppr p => p -> Doc
ppr0       = atPrec 0 . ppr

-- | Print at next level.
ppr1      :: Ppr p => p -> Doc
ppr1       = prec1 . ppr

-- | Print at one more than the given level.
pprPrec1  :: Ppr p => Int -> p -> Doc
pprPrec1   = pprPrec . succ

-- | Print to the given depth.
pprDepth  :: Ppr p => Int -> p -> Doc
pprDepth d = atDepth d . ppr

-- | Enter the given precedence level, drawing parentheses if necessary,
--   and count it as a descent in depth as well.
prec :: Int -> Doc -> Doc
prec p doc = asksD pcPrec $ \p' ->
  if p' > p
    then descend $ parens (atPrec p doc)
    else atPrec p doc

-- | Enter the given precedence level, drawing parentheses if necessary,
--   and count it as a descent in depth as well. If we enter
--   parentheses, reset the precedence to 0 at most.
prec0 :: Int -> Doc -> Doc
prec0 p doc = asksD pcPrec $ \p' ->
  if p' > p
    then descend $ parens (atPrec (p `min` 0) doc)
    else atPrec p doc

-- | Adjust the precedence with the given function.
mapPrec :: (Int -> Int) -> Doc -> Doc
mapPrec f doc = askPrec (\p -> prec (f p) doc)

-- | Go to the next (tigher) precedence level.
prec1 :: Doc -> Doc
prec1  = mapD (\e -> e { pcPrec = pcPrec e + 1 })

-- | Descend a level, elliding if the level counter runs out
descend :: Doc -> Doc
descend doc = askD $ \e ->
  case pcDepth e of
    -1 -> doc
    0  -> text Strings.ellipsis
    k  -> localD e { pcDepth = k - 1 } doc

-- | Set the precedence, but check or draw parentheses
atPrec   :: Int -> Doc -> Doc
atPrec p  = mapD (\e -> e { pcPrec = p })

-- | Set the precedence, but check or draw parentheses
atDepth  :: Int -> Doc -> Doc
atDepth k = mapD (\e -> e { pcDepth = k })

-- | Find out the precedence
askPrec :: (Int -> Doc) -> Doc
askPrec  = asksD pcPrec

-- | A conditional: uses the second argument if the current precedence
--   satisfies the predicate, otherwise the second
ifPrec  :: (Int -> Bool) -> Doc -> Doc -> Doc
ifPrec predicate true false =
  askPrec $ \p → if predicate p then true else false

-- | Find out the depth
askDepth :: (Int -> Doc) -> Doc
askDepth  = asksD pcDepth

-- | A conditional: uses the second argument if the current depth
--   satisfies the predicate, otherwise the second
ifDepth  :: (Int -> Bool) -> Doc -> Doc -> Doc
ifDepth predicate true false =
  askDepth $ \p → if predicate p then true else false

-- | Change the type name lookup function
setTyNames   :: TyNames -> Doc -> Doc
setTyNames f  = mapD (\e -> e { pcTyName = f })

-- | Retrieve the type name lookup function
askTyNames   :: (TyNames -> Doc) -> Doc
askTyNames    = asksD pcTyName

-- | Render a document with a module opened
enterTyNames :: ModId Renamed -> Doc -> Doc
enterTyNames u doc = askTyNames $ \tn ->
  setTyNames (tnEnter tn u) doc

-- | Look up a type name in the rendering context
lookupTyNames :: Int -> QTypId Renamed -> (QTypId Renamed -> Doc) -> Doc
lookupTyNames tag ql kont = askTyNames $ \tn ->
  kont (tnLookup tn tag ql)

-- | Trim a list to (about) the given number of elements, with
--   "..." in the middle.
trimList :: Int -> [Doc] -> [Doc]
trimList (-1) ds = ds
trimList n2   ds = if k <= 2 * n
                     then ds
                     else take n ds ++ text "... " : drop (k - n) ds
  where
    n = (n2 + 1) `div` 2
    k = length ds

-- | Lift a concatenation function to respect depth.
trimCat :: ([Doc] -> Doc) -> [Doc] -> Doc
trimCat xcat docs = asksD pcDepth $ \d -> case d of
  -1 -> xcat docs
  _  -> atDepth ((d + 1) `div` 2) (xcat (trimList d docs))

vcat, sep, cat, fsep, fcat :: [Doc] -> Doc
vcat = trimCat P.vcat
sep  = trimCat P.sep
cat  = trimCat P.cat
fsep = trimCat P.fsep
fcat = trimCat P.fcat

newtype MAYBE a = MAYBE (Maybe a) deriving (Eq, Ord)

instance Ppr a => Ppr (MAYBE a) where
  ppr (MAYBE Nothing)  = text "nothing"
  ppr (MAYBE (Just a)) = ppr a

instance Ppr a => Ppr (Maybe a) where
  ppr Nothing  = mempty
  ppr (Just a) = ppr a

instance Ppr a => Ppr (Perhaps a) where
  ppr Nope     = mempty
  ppr (Here a) = ppr a

instance (Ppr a, Ppr b) => Ppr (Either a b) where
  ppr (Left a)  = prec precApp (text "Left" <+> ppr a)
  ppr (Right a) = prec precApp (text "Right" <+> ppr a)

instance Ppr a => Ppr [a] where
  ppr = pprList

instance (Ppr a, Ppr b) => Ppr (a, b) where
  ppr (a, b) = parens (sep (punctuate comma [ppr0 a, ppr0 b]))

instance (Ppr a, Ppr b, Ppr c) => Ppr (a, b, c) where
  ppr (a,b,c) =
    parens (sep (punctuate comma [ppr0 a, ppr0 b, ppr0 c]))

instance (Ppr a, Ppr b, Ppr c, Ppr d) => Ppr (a, b, c, d) where
  ppr (a,b,c,d) =
    parens (sep (punctuate comma [ppr0 a, ppr0 b, ppr0 c, ppr0 d]))

instance (Ppr k, Ppr v) => Ppr (M.Map k v) where
  ppr m = braces . fsep . punctuate comma $
    [ ppr0 k <> colon <+> ppr0 v
    | (k, v) <- M.toList m ]

instance Ppr a => Ppr (S.Set a) where
  ppr = braces . fsep . punctuate comma . map ppr0 . S.toList

-- | Class to check if a particular thing will print infix.  Adds
--   an operation to print at the given precedence only if the given
--   thing is infix.  (We use this for printing arrows without too
--   many parens.)
class Ppr a => IsInfix a where
  isInfix  :: a -> Bool
  pprRight :: a -> Doc
  pprRight a =
    if isInfix a
      then ppr a
      else ppr0 a

instance Ppr Bool      where pprPrec = pprFromShow
instance Ppr Int       where ppr = int
instance Ppr Integer   where ppr = integer
instance Ppr Double    where ppr = double

instance Ppr Char where
  pprPrec        = pprFromShow
  pprStyleList _ = text

instance Ppr (P.Doc PprContext)  where ppr = id
instance Show (P.Doc PprContext) where showsPrec = showFromPpr

-- Render a document in the preferred style, given a string continuation
renderS :: Doc -> ShowS
renderS doc rest = fullRenderIn pprContext0 PageMode 80 1.1 each rest doc
  where each (Chr c) s'  = c:s'
        each (Str s) s'  = s++s'
        each (PStr s) s' = s++s'

-- Render a document in the preferred style
render :: Doc -> String
render doc = renderS doc ""

-- Is the document empty (in 'pprContext0')?
isEmpty :: Doc -> Bool
isEmpty  = isEmptyIn pprContext0

-- Render in the given style (in 'pprContext0')
renderStyle :: Style -> Doc -> String
renderStyle = renderStyleIn pprContext0

-- Render with the given parameters (in 'pprContext0')
fullRender :: Mode -> Int -> Float ->
              (TextDetails -> a -> a) -> a ->
              Doc -> a
fullRender = fullRenderIn pprContext0

-- Render and display a document in the preferred style
printDoc :: Doc -> IO ()
printDoc  = hPrintDoc stdout

-- Pretty-print, render and display in the preferred style
printPpr :: Ppr a => a -> IO ()
printPpr  = hPrintPpr stdout

-- Render and display a document in the preferred style
hPrintDoc :: Handle -> Doc -> IO ()
hPrintDoc h = fullRenderIn pprContext0 PageMode 80 1.1 each (putChar '\n')
  where each (Chr c) io  = hPutChar h c >> io
        each (Str s) io  = hPutStr h s >> io
        each (PStr s) io = hPutStr h s >> io

hPrintPpr :: Ppr a => Handle -> a -> IO ()
hPrintPpr h = hPrintDoc h . ppr

showFromPpr :: Ppr a => Int -> a -> ShowS
showFromPpr p t = renderS (pprPrec p t)

pprFromShow :: Show a => Int -> a -> Doc
pprFromShow p t = text (showsPrec p t "")

--
-- Some indentation operations
--

liftEmpty :: (Doc -> Doc -> Doc) -> Doc -> Doc -> Doc
liftEmpty joiner d1 d2 = askD f where
  f e | isEmptyIn e d1 = d2
      | isEmptyIn e d2 = d1
      | otherwise      = joiner d1 d2

ifEmpty :: Doc -> Doc -> Doc -> Doc
ifEmpty dc dt df = askD $ \e ->
  if isEmptyIn e dc
    then dt
    else df

(>+>) :: Doc -> Doc -> Doc
(>+>) = flip hang 2

(>?>) :: Doc -> Doc -> Doc
(>?>)  = liftEmpty (>+>)

infixr 5 >+>, >?>