Agda-2.8.0.1: src/full/Agda/Syntax/Common/Pretty.hs
{-| Pretty printing functions.
-}
module Agda.Syntax.Common.Pretty
( module Agda.Syntax.Common.Pretty
, module Text.PrettyPrint.Annotated
-- This re-export can be removed once <GHC-8.4 is dropped.
, module Data.Semigroup
) where
import Prelude hiding (null)
import qualified Data.List as List
import qualified Data.Foldable as Fold
import qualified Data.IntSet as IntSet
import qualified Data.IntMap as IntMap
import qualified Data.Text as T
import qualified Data.Map as Map
import qualified Data.Set as Set
import Data.IntSet (IntSet)
import Data.IntMap (IntMap)
import Data.Word (Word64, Word32)
import Data.Text (Text)
import Data.Int (Int32)
import Data.Map (Map)
import Data.Set (Set)
import qualified Text.PrettyPrint.Annotated as P
import Text.PrettyPrint.Annotated hiding
( Doc, TextDetails(Str), empty, (<>), sep, fsep, hsep, hcat, vcat, punctuate
, parens, brackets, braces, quotes, doubleQuotes
, semi, comma, colon, space, equals, lparen, rparen, lbrack, rbrack
, lbrace, rbrace
)
import Data.Semigroup ((<>))
import Agda.Utils.Float
import Agda.Utils.List1 (List1)
import qualified Agda.Utils.List1 as List1
import qualified Agda.Utils.Maybe.Strict as Strict
import Agda.Utils.Null
import Agda.Utils.Size
import Agda.Syntax.Common.Aspect
import Agda.Syntax.Position
import Agda.Utils.Impossible
import Agda.Utils.FileName
-- * Pretty class
-- | The type of documents. We use documents annotated by 'Aspects' to
-- record syntactic highlighting information that is generated during
-- pretty-printing.
type Doc = P.Doc Aspects
-- | While 'Show' is for rendering data in Haskell syntax,
-- 'Pretty' is for displaying data to the world, i.e., the
-- user and the environment.
--
-- Atomic data has no inner document structure, so just
-- implement 'pretty' as @pretty a = text $ ... a ...@.
class Pretty a where
pretty :: a -> Doc
prettyPrec :: Int -> a -> Doc
prettyList :: [a] -> Doc
pretty = prettyPrec 0
prettyPrec = const pretty
prettyList = brackets . prettyList_
{-# MINIMAL pretty | prettyPrec #-}
-- | Use instead of 'show' when printing to world.
prettyShow :: Pretty a => a -> String
prettyShow = render . pretty
-- * Pretty instances
instance Pretty Bool where pretty = text . show
instance Pretty Int where pretty = text . show
instance Pretty Int32 where pretty = text . show
instance Pretty Integer where pretty = text . show
instance Pretty Word32 where pretty = text . show
instance Pretty Word64 where pretty = text . show
instance Pretty Double where pretty = text . toStringWithoutDotZero
instance Pretty Text where pretty = text . T.unpack
instance Pretty Char where
pretty c = text [c]
prettyList = text
-- The equational constraint forces GHC to pick this instance and unify
-- the type variable, instead of deferring selection to when the type of
-- annotations is solved.
instance a ~ Aspects => Pretty (P.Doc a) where
pretty = id
instance Pretty () where
pretty _ = P.empty
instance (Pretty a, Pretty b) => Pretty (a, b) where
pretty (a, b) = parens $ (pretty a <> comma) <+> pretty b
instance Pretty a => Pretty (Maybe a) where
prettyPrec p Nothing = P.empty
prettyPrec p (Just x) = prettyPrec p x
instance Pretty a => Pretty [a] where
pretty = prettyList
instance Pretty a => Pretty (List1 a) where
pretty = prettyList . List1.toList
instance Pretty IntSet where
pretty = prettySet . IntSet.toList
instance Pretty a => Pretty (Set a) where
pretty = prettySet . Set.toList
instance Pretty a => Pretty (IntMap a) where
pretty = prettyMap . IntMap.toList
instance (Pretty k, Pretty v) => Pretty (Map k v) where
pretty = prettyMap . Map.toList
-- Pretty instances for dependencies of this module (to avoid dependency cycles)
instance Pretty AbsolutePath where
pretty = text . filePath
instance Pretty RangeFile where
pretty = pretty . rangeFilePath
prettyLineColumn :: Position' a -> Doc
prettyLineColumn (Pn _ _ l c) = pretty l <> dot <> pretty c
instance Pretty a => Pretty (Position' (Strict.Maybe a)) where
pretty p = case srcFile p of
Strict.Nothing -> prettyLineColumn p
Strict.Just f -> pretty f <> colon <> prettyLineColumn p
instance Pretty PositionWithoutFile where
pretty = prettyLineColumn
instance Pretty IntervalWithoutFile where
pretty (Interval () s e)
| s == e = start
| otherwise = start <> "-" <> end
where
sl = posLine s
el = posLine e
sc = posCol s
ec = posCol e
start :: Doc
start = pretty sl <> dot <> pretty sc
end :: Doc
| sl == el = pretty ec
| otherwise = pretty el <> dot <> pretty ec
instance Pretty a => Pretty (Interval' (Strict.Maybe a)) where
pretty i@(Interval f s e) = file <> pretty (Interval () s e)
where
file :: Doc
file = case f of
Strict.Nothing -> empty
Strict.Just f -> pretty f <> colon
instance Pretty a => Pretty (Range' (Strict.Maybe a)) where
pretty r = maybe empty pretty (rangeToIntervalWithFile r)
instance (Pretty a, HasRange a) => Pretty (PrintRange a) where
pretty (PrintRange a) = pretty a <+> parens ("at" <+> pretty (getRange a))
-- * Generalizing the original type from list to Foldable
sep, fsep, hsep, hcat, vcat :: Foldable t => t Doc -> Doc
sep = P.sep . Fold.toList
fsep = P.fsep . Fold.toList
hsep = P.hsep . Fold.toList
hcat = P.hcat . Fold.toList
vcat = P.vcat . Fold.toList
punctuate :: Foldable t => Doc -> t Doc -> [Doc]
punctuate d = P.punctuate d . Fold.toList
-- * 'Doc' utilities
vsep :: [Doc] -> Doc
vsep = vcat . List.intersperse ""
pwords :: String -> [Doc]
pwords = map text . words
fwords :: String -> Doc
fwords = fsep . pwords
-- | Separate, but only if both separees are not null.
hsepWith :: Doc -> Doc -> Doc -> Doc
hsepWith sep d1 d2
| null d2 = d1
| null d1 = d2
| otherwise = d1 <+> sep <+> d2
-- | Comma separated list, without the brackets.
prettyList_ :: Pretty a => [a] -> Doc
prettyList_ = fsep . punctuate comma . map pretty
-- | Pretty print a set.
prettySet :: Pretty a => [a] -> Doc
prettySet = braces . prettyList_
-- | Pretty print an association list.
prettyMap :: (Pretty k, Pretty v) => [(k,v)] -> Doc
prettyMap = braces . fsep . punctuate comma . map prettyAssign
-- | Pretty print a single association.
prettyAssign :: (Pretty k, Pretty v) => (k,v) -> Doc
prettyAssign (k, v) = sep [ prettyPrec 1 k <+> hlSymbol "->", nest 2 $ pretty v ]
-- ASR (2016-12-13): In pretty >= 1.1.2.0 the below function 'mparens'
-- is called 'maybeParens'. I didn't use that name due to the issue
-- https://github.com/haskell/pretty/issues/40.
-- | Apply 'parens' to 'Doc' if boolean is true.
mparens :: Bool -> Doc -> Doc
mparens True = parens
mparens False = id
-- | Only wrap in parens if not 'empty'
parensNonEmpty :: Doc -> Doc
parensNonEmpty d = if null d then empty else parens d
-- | Return 'empty' for empty strings.
textNonEmpty :: String -> Doc
textNonEmpty = \case
"" -> empty
s -> text s
-- | @align max rows@ lays out the elements of @rows@ in two columns,
-- with the second components aligned. The alignment column of the
-- second components is at most @max@ characters to the right of the
-- left-most column.
--
-- Precondition: @max > 0@.
align :: Int -> [(String, Doc)] -> Doc
align max rows =
vcat $ map (\(s, d) -> text s $$ nest (maxLen + 1) d) $ rows
where maxLen = maximum $ 0 : filter (< max) (map (length . fst) rows)
-- | Handles strings with newlines properly (preserving indentation)
multiLineText :: String -> Doc
multiLineText = vcat . map text . lines
infixl 6 <?>
-- | @a <?> b = hang a 2 b@
(<?>) :: Doc -> Doc -> Doc
a <?> b = hang a 2 b
-- | @pshow = text . show@
pshow :: Show a => a -> Doc
pshow = text . show
singPlural :: Sized a => a -> c -> c -> c
singPlural xs singular plural = if natSize xs == 1 then singular else plural
pluralS :: Sized a => a -> Doc -> Doc
pluralS xs d = singPlural xs d (d <> "s")
-- | Used for with-like 'telescopes'
prefixedThings :: Doc -> [Doc] -> Doc
prefixedThings kw = \case
[] -> P.empty
(doc : docs) -> fsep $ (kw <+> doc) : map (hlSymbol "|" <+>) docs
-- | Attach a simple 'Aspect', rather than a full set of 'Aspects', to a
-- document.
annotateAspect :: Aspect -> Doc -> Doc
annotateAspect a = annotate a' where
a' = Aspects
{ aspect = Just a
, otherAspects = mempty
, note = ""
, definitionSite = Nothing
, tokenBased = TokenBased
}
-- * Syntax highlighting helpers
hlComment, hlSymbol, hlKeyword, hlString, hlNumber, hlHole, hlPrimitiveType, hlPragma
:: Doc -> Doc
hlComment = annotateAspect Comment
hlSymbol = annotateAspect Symbol
hlKeyword = annotateAspect Keyword
hlString = annotateAspect String
hlNumber = annotateAspect Number
hlHole = annotateAspect Hole
hlPrimitiveType = annotateAspect PrimitiveType
hlPragma = annotateAspect Pragma
-- * Delimiter wrappers
--
-- These use the 'Symbol' highlight for the punctuation characters.
parens :: Doc -> Doc -- ^ Wrap document in @(...)@
brackets :: Doc -> Doc -- ^ Wrap document in @[...]@
braces :: Doc -> Doc -- ^ Wrap document in @{...}@
quotes :: Doc -> Doc -- ^ Wrap document in @\'...\'@
doubleQuotes :: Doc -> Doc -- ^ Wrap document in @\"...\"@
quotes p = hlSymbol (char '\'') <> p <> hlSymbol (char '\'')
doubleQuotes p = hlSymbol (char '"') <> p <> hlSymbol (char '"')
parens p = lparen <> p <> rparen
brackets p = lbrack <> p <> rbrack
braces p = lbrace <> p <> rbrace
semi, comma, colon, dot, space, equals, lparen, rparen, lbrack, rbrack, lbrace, rbrace :: Doc
semi = hlSymbol $ char ';'
comma = hlSymbol $ char ','
colon = hlSymbol $ char ':'
dot = hlSymbol $ char '.'
space = hlSymbol $ char ' '
equals = hlSymbol $ char '='
lparen = hlSymbol $ char '('
rparen = hlSymbol $ char ')'
lbrack = hlSymbol $ char '['
rbrack = hlSymbol $ char ']'
lbrace = hlSymbol $ char '{'
rbrace = hlSymbol $ char '}'