packages feed

alms-0.4.9: src/Ppr.hs

-- | Pretty-printing
{-# LANGUAGE
      PatternGuards,
      QuasiQuotes,
      TypeSynonymInstances
    #-}
module Ppr (
  -- * Pretty-printing class
  Ppr(..),
  -- * Pretty-printing combinators
  parensIf, (>+>), (>?>), pprTyApp,
  -- * Renderers
  render, renderS, printDoc, printPpr,
  -- ** Instance helpers
  showFromPpr, pprFromShow,
  -- * Re-exports
  module Text.PrettyPrint,
  module Prec
) where

import Meta.Quasi
import Prec
import Syntax
import Util

import qualified Syntax.Decl
import qualified Syntax.Expr
import qualified Syntax.Notable
import qualified Syntax.Patt
import qualified Loc

import Text.PrettyPrint hiding (render)
import Data.List (intersperse)

-- | Class for pretty-printing at different types
--
-- Minimal complete definition is one of:
--
-- * 'pprPrec'
--
-- * 'ppr'
class Ppr p where
  -- | Print at the specified enclosing precedence
  pprPrec :: Int -> p -> Doc
  -- | Print at top-level precedence
  ppr     :: p -> Doc
  -- | Print a list at the specified enclosing precedence with
  --   the specified style
  pprPrecStyleList :: Int -> ListStyle -> [p] -> Doc
  -- | Print a list at the specified enclosing precedence
  pprPrecList :: Int -> [p] -> Doc
  -- | Print a list at top-level precedence
  pprList     :: [p] -> Doc
  -- | Style for printing lists
  listStyle   :: [p] -> ListStyle
  --
  ppr         = pprPrec precDot
  pprPrec _   = ppr
  pprPrecStyleList _ st [] =
    if listStyleDelimitEmpty st
      then listStyleBegin st <> listStyleEnd st
      else empty
  pprPrecStyleList p st [x] =
    if listStyleDelimitSingleton st
      then listStyleBegin st <> ppr x <> listStyleEnd st
      else pprPrec p x
  pprPrecStyleList _ st xs  =
    listStyleBegin st <>
      listStyleJoiner st (punctuate (listStylePunct st) (map ppr xs))
    <> listStyleEnd st
  pprPrecList p xs = pprPrecStyleList p (listStyle xs) xs
  pprList          = pprPrecList 0
  listStyle _ = ListStyle {
    listStyleBegin            = lparen,
    listStyleEnd              = rparen,
    listStylePunct            = comma,
    listStyleDelimitEmpty     = False,
    listStyleDelimitSingleton = False,
    listStyleJoiner           = fsep
  }

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

-- | Conditionally add parens around the given 'Doc'
parensIf :: Bool -> Doc -> Doc
parensIf True  doc = parens doc
parensIf False doc = doc

instance Ppr a => Ppr [a] where
  pprPrec = pprPrecList

instance Ppr a => Ppr (Maybe a) where
  pprPrec _ Nothing  = empty
  pprPrec p (Just a) = pprPrec p a

class Ppr a => IsInfix a where
  isInfix  :: a -> Bool
  pprRight :: Int -> a -> Doc
  pprRight p a =
    if isInfix a
      then pprPrec p a
      else ppr a

instance Ppr Doc where
  ppr = id

instance IsInfix (Type i) where
  isInfix [$ty| ($_, $_) $lid:n |] = isOperator n
  isInfix [$ty| $_ -[$_]> $_ |]    = True
  isInfix _                        = False

-- | To pretty print the application of a type constructor to
--   generic parameters
pprTyApp :: (Ppr a) => Int -> QLid i -> [a] -> Doc
pprTyApp _ ql       []   = ppr ql
pprTyApp p (J [] l) [t1]
  | isOperator l, precOp (unLid l) == Right precBang
    = parensIf (p > precBang) $
        text (unLid l) <> pprPrec (precBang + 1) t1
pprTyApp p (J [] l) [t1, t2]
  -- print @ without space around it:
  | isOperator l, '@':_ <- unLid l, Right prec <- precOp (unLid l)
    = parensIf (p > prec) $
        pprPrec (prec + 1) t1 <> text (unLid l) <> pprPrec prec t2
  | isOperator l, Left prec <- precOp (unLid l)
    = parensIf (p > prec) $
        sep [ pprPrec prec t1,
              text (unLid l) <+> pprPrec (prec + 1) t2 ]
  | isOperator l, Right prec <- precOp (unLid l)
    = parensIf (p > prec) $
        sep [ pprPrec (prec + 1) t1,
              text (unLid l) <+> pprPrec prec t2]
pprTyApp p ql ts = parensIf (p > precApp) $
                     sep [ pprPrec precApp ts,
                           ppr ql ]

instance Ppr (Type i) where
  -- Print sugar for infix type constructors:
  pprPrec p [$ty| $t1 ; $t2 |]
                  = parensIf (p > precSemi) $
                      sep [ pprPrec (precSemi + 1) t1 <> text ";",
                            pprPrec precSemi t2 ]
  -- pprPrec p (TyFun q t1 t2)
  pprPrec p [$ty| $t1 -[$q]> $t2 |]
                  = parensIf (p > precArr) $
                    sep [ pprPrec (precArr + 1) t1,
                          pprArr (view q) <+> pprRight precArr t2 ]
    where pprArr (QeLit Qu) = text "->"
          pprArr (QeLit Qa) = text "-o"
          pprArr _          = text "-[" <> pprPrec precStart q <> text "]>"
  pprPrec p [$ty| ($list:ts) $qlid:n |]
                          = pprTyApp p n ts
    -- debugging: <> text (show (ttId (unsafeCoerce tag :: TyTag)))
  pprPrec p [$ty| '$x |]  = pprPrec p x
  pprPrec p [$ty| $quant:qu '$x. $t |]
                          = parensIf (p > precDot) $
                              ppr qu <+>
                              fsep (map (pprPrec (precDot + 1))
                                        tvs) <>
                              char '.'
                                >+> pprPrec precDot body
      where (tvs, body) = unfoldTyQu qu [$ty| $quant:qu '$x. $t |]
  pprPrec p [$ty| mu '$x. $t |]
                          = parensIf (p > precDot) $
                              text "mu" <+>
                              pprPrec (precDot + 1) x <>
                              char '.'
                                >+> pprPrec precDot t
  pprPrec p [$ty| $anti:a |] = pprPrec p a

instance Ppr (TyPat i) where
  pprPrec p tp0 = case tp0 of
    N _ (TpVar tv var) -> pprParamV var tv
    [$tpQ| ($list:tps) $qlid:ql |]
                       -> pprTyApp p ql tps
    [$tpQ| $antiP:a |] -> ppr a

instance Ppr (QExp i) where
  pprPrec p [$qeQ| $qlit:qu |] = pprPrec p qu
  pprPrec p [$qeQ| $qvar:v |]  = pprPrec p (tvname v)
  pprPrec p [$qeQ| $qdisj:qes |] = case qes of
    []    -> pprPrec p Qu
    [qe]  -> pprPrec p qe
    _     -> parensIf (p > precPlus) $
               fsep $
                 intersperse (text "\\/") $
                   map (pprPrec (precPlus + 1)) qes
  pprPrec p [$qeQ| $qconj:qes |] = case qes of
    []    -> pprPrec p Qa
    [qe]  -> pprPrec p qe
    _     -> parensIf (p > precStar) $
               hcat $
                 intersperse (text "/\\") $
                   map (pprPrec (precStar + 1)) qes
  pprPrec p [$qeQ| $anti:a |] = pprPrec p a

instance Ppr Int where
  ppr = int

instance Ppr (Prog i) where
  ppr [$prQ| $list:ms |]       = vcat (map ppr ms)
  ppr [$prQ| $expr:e |]        = ppr e
  ppr [$prQ| $list:ms in $e |] = vcat (map ppr ms) $+$
                                 (text "in" >+> ppr e)

instance Ppr (Decl i) where
  ppr [$dc| let $x = $e |] = sep
    [ text "let" <+> ppr x,
      nest 2 $ equals <+> ppr e ]
  ppr [$dc| let $x : $t = $e |] = sep
    [ text "let" <+> ppr x,
      nest 2 $ colon <+> ppr t,
      nest 4 $ equals <+> ppr e ]
  ppr [$dc| type $list:tds |] = pprTyDecs tds
  ppr [$dc| abstype $list:ats0 with $list:ds end |] =
    case ats0 of
      []     ->
        vcat [
          text "abstype with",
          nest 2 $ vcat (map ppr ds),
          text "end"
        ]
      at:ats ->
        vcat [
          vcat (text "abstype" <+> pprAbsTy at :
                [ nest 4 $ text "and" <+> pprAbsTy ati | ati <- ats ])
            <+> text "with",
          nest 2 $ vcat (map ppr ds),
          text "end"
        ]
  ppr [$dc| open $b |] = pprModExp (text "open" <+>) b
  ppr [$dc| module $uid:n : $s = $b |] = pprModExp add1 b where
    add1 body = pprSigExp add2 s <+> equals <+> body
    add2 body = text "module" <+> ppr n <+> colon <+> body
  ppr [$dc| module $uid:n = $b |] = pprModExp add b where
    add body = text "module" <+> ppr n <+> equals <+> body
  ppr [$dc| module type $uid:n = $s |] = pprSigExp add s where
    add body = text "module type" <+> ppr n <+> equals <+> body
  ppr [$dc| local $list:d0 with $list:d1 end |] =
    vcat [
      text "local",
      nest 2 (vcat (map ppr d0)),
      text "with",
      nest 2 (vcat (map ppr d1)),
      text "end"
    ]
  ppr [$dc| exception $uid:n of $opt:mt |] =
    pprExcDec n mt
  ppr [$dc| $anti:a |] = ppr a

pprTyDecs :: [TyDec i] -> Doc
pprTyDecs tds =
  vcat $
    mapHead (text "type" <+>) $
      mapTail ((nest 1) . (text "and" <+>)) $
        map ppr tds

pprExcDec :: Uid i -> Maybe (Type i) -> Doc
pprExcDec u Nothing  =
  text "exception" <+> ppr u
pprExcDec u (Just t) =
  text "exception" <+> ppr u <+> text "of" <+> ppr t

instance Ppr (TyDec i) where
  ppr td = case view td of
    TdAbs n ps vs qs  -> pprProtoV n vs ps >?> pprQuals qs
    TdSyn n [(ps,t)]  -> pprProto n ps >+> equals <+> ppr t
    TdSyn n cs        -> vcat [ char '|' <+> each ci | ci <- cs ]
      where
        each (ps, rhs) = pprProto n ps
                           >+> char '=' <+> ppr rhs
    TdDat n ps alts   -> pprProtoV n (repeat Invariant) ps
                           >?> pprAlternatives alts
    TdAnti a          -> ppr a

pprAbsTy :: AbsTy i -> Doc
pprAbsTy at = case view at of
  AbsTy variances qual (N _ (TdDat name params alts)) ->
    pprProtoV name variances params
      >?> pprQuals qual
      >?> pprAlternatives alts
  AbsTy _ _ td -> ppr td -- shouldn't happen (yet)
  AbsTyAnti a -> ppr a

pprProto  :: Lid i -> [TyPat i] -> Doc
pprProto n ps = ppr (tpApp (J [] n) ps)

pprProtoV :: Lid i -> [Variance] -> [TyVar i] -> Doc
pprProtoV n vs tvs = pprProto n (zipWith tpVar tvs vs)

pprParamV :: Variance -> TyVar i -> Doc
pprParamV Invariant tv = ppr tv
pprParamV v         tv = ppr v <> ppr tv

pprQuals :: QExp i -> Doc
pprQuals [$qeQ| U |] = empty
pprQuals qs          = text ":" <+> pprPrec precApp qs

pprAlternatives :: [(Uid i, Maybe (Type i))] -> Doc
pprAlternatives [] = equals
pprAlternatives (a:as) = sep $
  equals <+> alt a : [ char '|' <+> alt a' | a' <- as ]
  where
    alt (u, Nothing) = ppr u
    alt (u, Just t)  = ppr u <+> text "of" <+> pprPrec precDot t

pprModExp :: (Doc -> Doc) -> ModExp i -> Doc
pprModExp add modexp = case modexp of
  [$me| $quid:n |] -> add (ppr n)
  [$me| $quid:n $list:qls |] ->
    add (ppr n) <+>
    brackets (fsep (punctuate comma (map ppr qls)))
  [$me| struct $list:ds end |] ->
    add (text "struct")
    $$ nest 2 (vcat (map ppr ds))
    $$ text "end"
  [$me| $me1 : $se2 |] ->
    pprSigExp (pprModExp add me1 <+> colon <+>) se2
  [$me| $anti:a |] -> add (ppr a)

pprSigExp :: (Doc -> Doc) -> SigExp i -> Doc
pprSigExp add se0 = body >+> withs where
  (wts, se1) = unfoldSeWith se0
  body       = case se1 of
    [$seQ| $quid:n |] -> add (ppr n)
    [$seQ| $quid:n $list:qls |] ->
      add (ppr n) <+>
      brackets (fsep (punctuate comma (map ppr qls)))
    [$seQ| sig $list:sgs end |] ->
      add (text "sig")
      $$ nest 2 (vcat (map ppr sgs))
      $$ text "end"
    [$seQ| $_ with type $list:_ $qlid:_ = $_ |] ->
      error "BUG! can't happen in pprSigExp"
    [$seQ| $anti:a |] -> add (ppr a)
  withs      =
    sep $
      mapHead (text "with type" <+>) $
        mapTail ((nest 6) . (text "and" <+>)) $
          [ pprTyApp 0 tc tvs <+> equals <+> ppr t
          | (tc, tvs, t) <- wts ]

instance Ppr (SigItem i) where
  ppr sg0 = case sg0 of
    [$sgQ| val $lid:n : $t |] ->
      text "val" <+> ppr n >+> colon <+> ppr t
    [$sgQ| type $list:tds |] ->
      pprTyDecs tds
    [$sgQ| module $uid:u : $s |] ->
      pprSigExp add s where
        add body = text "module" <+> ppr u <+> colon <+> body
    [$sgQ| module type $uid:u = $s |] ->
      pprSigExp add s where
        add body = text "module type" <+> ppr u <+> equals <+> body
    [$sgQ| include $s |] ->
      pprSigExp (text "include" <+>) s
    [$sgQ| exception $uid:u of $opt:mt |] ->
      pprExcDec u mt
    [$sgQ| $anti:a |] ->
      ppr a

instance Ppr (Expr i) where
  pprPrec p e0 = case e0 of
    [$ex| $id:x |]   -> pprPrec p x
    [$ex| $lit:lt |] -> pprPrec p lt
    [$ex| if $ec then $et else $ef |] ->
      parensIf (p > precDot) $
        sep [ text "if" <+> ppr ec,
              nest 2 $ text "then" <+> ppr et,
              nest 2 $ text "else" <+> pprPrec precDot ef ]
    [$ex| $e1; $e2 |] ->
      parensIf (p > precSemi) $
        sep [ pprPrec (precSemi + 1) e1 <> semi,
              ppr e2 ]
    [$ex| let $x = $e1 in $e2 |] ->
      pprLet p (ppr x) e1 e2
    [$ex| match $e1 with $list:clauses |] ->
      parensIf (p > precDot) $
        vcat (sep [ text "match",
                    nest 2 $ ppr e1,
                    text "with" ] : map alt clauses)
      where
        alt (N _ (CaClause xi ei)) =
          hang (char '|' <+> pprPrec precDot xi <+> text "->")
                4
                (pprPrec precDot ei)
        alt (N _ (CaAnti a))      = char '|' <+> ppr a
    [$ex| let rec $list:bs in $e2 |] ->
      text "let" <+>
      vcat (zipWith each ("rec" : repeat "and") bs) $$
      text "in" <+> pprPrec precDot e2
        where
          each kw (N _ (BnBind x t e)) =
            -- This could be better by pulling some args out.
            hang (hang (text kw <+> ppr x)
                       6
                       (colon <+> ppr t <+> equals))
                 2
                 (ppr e)
          each kw (N _ (BnAnti a)) = text kw <+> ppr a
    [$ex| let $decl:d in $e2 |] ->
      text "let" <+> ppr d $$
      (text "in" >+> pprPrec precDot e2)
    [$ex| ($e1, $e2) |] ->
      parensIf (p > precCom) $
        sep [ pprPrec precCom e1 <> comma,
              pprPrec (precCom + 1) e2 ]
    [$ex| fun $_ : $_ -> $_ |] -> pprAbs p e0
    [$ex| $name:x $e2 |]
      | Right p' <- precOp x,
        p' == 10
          -> parensIf (p > p') $
               text x <+> pprPrec p' e2
    [$ex| ($name:x $e12) $e2 |] 
      | (dl, dr, p') <- either ((,,) 0 1) ((,,) 1 0) (precOp x),
        p' < 9
          -> parensIf (p > p') $
               sep [ pprPrec (p' + dl) e12,
                     text x,
                     pprPrec (p' + dr) e2 ]
    [$ex| $e1 $e2 |]
          -> parensIf (p > precApp) $
               sep [ pprPrec precApp e1,
                     pprPrec (precApp + 1) e2 ]
    [$ex| fun '$_ -> $_ |] -> pprAbs p e0
    [$ex| $_ [$_] |] ->
      parensIf (p > precTApp) $
        cat [ pprPrec precTApp op,
              brackets . fsep . punctuate comma $
                map (pprPrec precCom) args ]
      where 
        (args, op) = unfoldExTApp e0
    [$ex| Pack[$opt:t1]($t2, $e) |] ->
      parensIf (p > precApp) $
        text "Pack" <> maybe empty (brackets . ppr) t1 <+>
        parens (sep [ pprPrec (precCom + 1) t2 <> comma,
                      pprPrec precCom e ])
    [$ex| ( $e : $t1 :> $t2 ) |] ->
      parensIf (p > precCast) $
         sep [ pprPrec (precCast + 2) e,
               colon     <+> pprPrec (precCast + 2) t1,
               text ":>" <+> pprPrec (precCast + 2) t2 ]
    [$ex| ( $e : $t1 ) |] ->
      parensIf (p > precCast) $
         sep [ pprPrec (precCast + 2) e,
               colon     <+> pprPrec (precCast + 2) t1 ]
    [$ex| ( $e :> $t1 ) |] ->
      parensIf (p > precCast) $
         sep [ pprPrec (precCast + 2) e,
               text ":>" <+> pprPrec (precCast + 2) t1 ]
    [$ex| $anti:a |] -> pprPrec p a

pprLet :: Int -> Doc -> Expr i -> Expr i -> Doc
pprLet p pat e1 e2 = parensIf (p > precDot) $
  hang (text "let" <+> pat <+> pprArgList args <+> equals
          >+> ppr body <+> text "in")
       (if isLet (view e2)
          then 0
          else 2)
       (pprPrec precDot e2)
  where
    (args, body) = unfoldExAbs e1
    isLet (ExCase _ [_]) = True
    isLet _              = False

pprAbs :: Int -> Expr i -> Doc
pprAbs p e = parensIf (p > precDot) $
    text "fun" <+> argsDoc <+> text "->"
      >+> pprPrec precDot body
  where (args, body)   = unfoldExAbs e
        argsDoc = case args of
          [Left ([$pa| _ |], [$ty|@! unit |])]
                        -> parens empty
          [Left (x, t)] -> ppr x <+> char ':' <+> pprPrec (precArr + 1) t
          _             -> pprArgList args

pprArgList :: [Either (Patt i, Type i) (TyVar i)] -> Doc
pprArgList = fsep . map eachArg . combine where
  eachArg (Left ([$pa| _ |], [$ty|@! unit |]))
                          = parens empty
  eachArg (Left (x, t))   = parens $
                              ppr x
                                >+> colon <+> ppr t
  eachArg (Right tvs)     = brackets .
                              sep .
                                punctuate comma $
                                  map ppr tvs

  combine :: [Either a b] -> [Either a [b]]
  combine  = foldr each [] where
    each (Right b) (Right bs : es) = Right (b : bs) : es
    each (Right b) es              = Right [b] : es
    each (Left a)  es              = Left a : es

instance Ppr (Patt i) where
  pprPrec _ [$pa| _ |]             = text "_"
  pprPrec _ [$pa| $lid:l |]        = ppr l
  pprPrec _ [$pa| $quid:qu |]      = ppr qu
  pprPrec p [$pa| $quid:qu $x |]   = parensIf (p > precApp) $
                                       pprPrec precApp qu <+>
                                       pprPrec (precApp + 1) x
  pprPrec p [$pa| ($x, $y) |]      = parensIf (p > precCom) $
                                       pprPrec precCom x <> comma <+>
                                       pprPrec (precCom + 1) y
  pprPrec p [$pa| $lit:lt |]       = pprPrec p lt
  pprPrec p [$pa| $x as $lid:l |]  = parensIf (p > precDot) $
                                       pprPrec (precDot + 1) x <+>
                                       text "as" <+> ppr l
  pprPrec p [$pa| Pack('$tv,$x) |] = parensIf (p > precApp) $
                                       text "Pack" <+> parens (sep pair)
    where pair = [ pprPrec (precCom + 1) tv <> comma,
                   pprPrec precCom x ]
  pprPrec p [$pa| $anti:a |]       = pprPrec p a

instance Ppr Lit where
  ppr (LtInt i)   = integer i
  ppr (LtFloat f) = double f
  ppr (LtStr s)   = text (show s)
  ppr (LtAnti a)  = ppr a

instance Show (Prog i)   where showsPrec = showFromPpr
instance Show (Decl i)   where showsPrec = showFromPpr
instance Show (TyDec i)  where showsPrec = showFromPpr
instance Show (Expr i)   where showsPrec = showFromPpr
instance Show (Patt i)   where showsPrec = showFromPpr
instance Show Lit        where showsPrec = showFromPpr
instance Show (Type i)   where showsPrec = showFromPpr
instance Show (TyPat i)  where showsPrec = showFromPpr
instance Show (QExp i)   where showsPrec = showFromPpr
instance Show (SigItem i)where showsPrec = showFromPpr

instance Ppr QLit      where pprPrec = pprFromShow
instance Ppr Variance  where pprPrec = pprFromShow
instance Ppr Quant     where pprPrec = pprFromShow
instance Ppr (Lid i)   where pprPrec = pprFromShow
instance Ppr (Uid i)   where pprPrec = pprFromShow
instance Ppr (BIdent i)where pprPrec = pprFromShow
instance Ppr (TyVar i) where pprPrec = pprFromShow
instance Ppr Anti      where pprPrec = pprFromShow
instance (Show p, Show k) => Ppr (Path p k) where pprPrec = pprFromShow

-- Render a document in the preferred style, given a string continuation
renderS :: Doc -> ShowS
renderS doc rest = fullRender PageMode 80 1.5 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 ""

-- Render and display a document in the preferred style
printDoc :: Doc -> IO ()
printDoc = fullRender PageMode 80 1.5 each (putChar '\n')
  where each (Chr c) io  = putChar c >> io
        each (Str s) io  = putStr s >> io
        each (PStr s) io = putStr s >> io

-- Pretty-print, render and display in the preferred style
printPpr :: Ppr a => a -> IO ()
printPpr = printDoc . 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 "")

liftEmpty :: (Doc -> Doc -> Doc) -> Doc -> Doc -> Doc
liftEmpty joiner d1 d2
  | isEmpty d1 = d2
  | isEmpty d2 = d1
  | otherwise  = joiner d1 d2

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

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

infixr 5 >+>, >?>