pretty-compact-2.0: Text/PrettyPrint/Compact.hs
{-# LANGUAGE OverloadedStrings #-}
module Text.PrettyPrint.Compact (
-- * Documents
Doc,
-- * Basic combinators
module Data.Monoid, text, flush, char,
hang, encloseSep, list, tupled, semiBraces,
-- * Operators
(<+>), ($$),
-- * List combinators
hsep, sep, hcat, vcat, cat, punctuate,
-- * Bracketing combinators
enclose, squotes, dquotes, parens, angles, braces, brackets,
-- * Character documents
lparen, rparen, langle, rangle, lbrace, rbrace, lbracket, rbracket,
squote, dquote, semi, colon, comma, space, dot, backslash, equals,
-- * Primitive type documents
string, int, integer, float, double, rational,
bool,
-- * Rendering
render,
-- * Undocumented
-- column, nesting, width
) where
import Data.Monoid
import Text.PrettyPrint.Compact.Core as Text.PrettyPrint.Compact
-- | The document @(list xs)@ comma separates the documents @xs@ and
-- encloses them in square brackets. The documents are rendered
-- horizontally if that fits the page. Otherwise they are aligned
-- vertically. All comma separators are put in front of the elements.
list :: [Doc] -> Doc
list = encloseSep lbracket rbracket comma
-- | The document @(tupled xs)@ comma separates the documents @xs@ and
-- encloses them in parenthesis. The documents are rendered
-- horizontally if that fits the page. Otherwise they are aligned
-- vertically. All comma separators are put in front of the elements.
tupled :: [Doc] -> Doc
tupled = encloseSep lparen rparen comma
-- | The document @(semiBraces xs)@ separates the documents @xs@ with
-- semi colons and encloses them in braces. The documents are rendered
-- horizontally if that fits the page. Otherwise they are aligned
-- vertically. All semi colons are put in front of the elements.
semiBraces :: [Doc] -> Doc
semiBraces = encloseSep lbrace rbrace semi
-- | The document @(enclosure l r sep xs)@ concatenates the documents
-- @xs@ separated by @sep@ and encloses the resulting document by @l@
-- and @r@. The documents are rendered horizontally if that fits the
-- page. Otherwise they are aligned vertically. All separators are put
-- in front of the elements. For example, the combinator 'list' can be
-- defined with @enclosure@:
--
-- > list xs = enclosure lbracket rbracket comma xs
-- > test = text "list" <+> (list (map int [10,200,3000]))
--
-- Which is layed out with a page width of 20 as:
--
-- @
-- list [10,200,3000]
-- @
--
-- But when the page width is 15, it is layed out as:
--
-- @
-- list [10
-- ,200
-- ,3000]
-- @
encloseSep :: Doc -> Doc -> Doc -> [Doc] -> Doc
encloseSep left right sep ds
= case ds of
[] -> left <> right
[d] -> left <> d <> right
_ -> cat (zipWith (<>) (left : repeat sep) ds) <> right
-----------------------------------------------------------
-- punctuate p [d1,d2,...,dn] => [d1 <> p,d2 <> p, ... ,dn]
-----------------------------------------------------------
-- | @(punctuate p xs)@ concatenates all documents in @xs@ with
-- document @p@ except for the last document.
--
-- > someText = map text ["words","in","a","tuple"]
-- > test = parens (align (cat (punctuate comma someText)))
--
-- This is layed out on a page width of 20 as:
--
-- @
-- (words,in,a,tuple)
-- @
--
-- But when the page width is 15, it is layed out as:
--
-- @
-- (words,
-- in,
-- a,
-- tuple)
-- @
--
-- (If you want put the commas in front of their elements instead of
-- at the end, you should use 'tupled' or, in general, 'encloseSep'.)
punctuate :: Doc -> [Doc] -> [Doc]
punctuate p [] = []
punctuate p [d] = [d]
punctuate p (d:ds) = (d <> p) : punctuate p ds
-----------------------------------------------------------
-- high-level combinators
-----------------------------------------------------------
-- | The document @(sep xs)@ concatenates all documents @xs@ either
-- horizontally with @(\<+\>)@, if it fits the page, or vertically with
-- @(\<$\>)@.
--
sep :: [Doc] -> Doc
sep [] = mempty
sep xs = hsep xs <|> vcat xs
-- | The document @(fillSep xs)@ concatenates documents @xs@
-- horizontally with @(\<+\>)@ as long as its fits the page, than
-- inserts a @line@ and continues doing that for all documents in
-- @xs@.
--
-- > fillSep xs = foldr (\<\/\>) empty xs
fillSep :: [Doc] -> Doc
fillSep = foldDoc (</>)
-- | The document @(hsep xs)@ concatenates all documents @xs@
-- horizontally with @(\<+\>)@.
hsep :: [Doc] -> Doc
hsep = foldDoc (<+>)
-- | The document @(cat xs)@ concatenates all documents @xs@ either
-- horizontally with @(\<\>)@, if it fits the page, or vertically with
-- @(\<$$\>)@.
--
-- > cat xs = group (vcat xs)
cat :: [Doc] -> Doc
cat [] = mempty
cat xs = hcat xs <> vcat xs
-- | The document @(fillCat xs)@ concatenates documents @xs@
-- horizontally with @(\<\>)@ as long as its fits the page, than inserts
-- a @linebreak@ and continues doing that for all documents in @xs@.
--
-- > fillCat xs = foldr (\<\/\/\>) empty xs
fillCat :: [Doc] -> Doc
fillCat = foldDoc (<//>)
-- | The document @(hcat xs)@ concatenates all documents @xs@
-- horizontally with @(\<\>)@.
hcat :: [Doc] -> Doc
hcat = foldDoc (<>)
-- | The document @(vcat xs)@ concatenates all documents @xs@
-- vertically with @($$)@.
vcat :: [Doc] -> Doc
vcat = foldDoc ($$)
foldDoc :: (Doc -> Doc -> Doc) -> [Doc] -> Doc
foldDoc _ [] = mempty
foldDoc f ds = foldr1 f ds
-- | The document @(x \<+\> y)@ concatenates document @x@ and @y@ with a
-- @space@ in between. (infixr 6)
(<+>) :: Doc -> Doc -> Doc
x <+> y = x <> space <> y
-- | The document @(x \<\/\> y)@ puts @x@ and @y@ either next to each other
-- (with a @space@ in between) or underneath each other. (infixr 5)
(</>) :: Doc -> Doc -> Doc
x </> y = ((x <> space) <|> flush x) <> y
-- | The document @(x \<\/\/\> y)@ puts @x@ and @y@ either right next to each
-- other or underneath each other. (infixr 5)
(<//>) :: Doc -> Doc -> Doc
x <//> y = (x <|> flush x) <> y
-- | The document @(x \<$$\> y)@ concatenates document @x@ and @y@ with
-- a linebreak in between. (infixr 5)
(<$$>) :: Doc -> Doc -> Doc
x <$$> y = flush x <> y
-- | Document @(squotes x)@ encloses document @x@ with single quotes
-- \"'\".
squotes :: Doc -> Doc
squotes = enclose squote squote
-- | Document @(dquotes x)@ encloses document @x@ with double quotes
-- '\"'.
dquotes :: Doc -> Doc
dquotes = enclose dquote dquote
-- | Document @(braces x)@ encloses document @x@ in braces, \"{\" and
-- \"}\".
braces :: Doc -> Doc
braces = enclose lbrace rbrace
-- | Document @(parens x)@ encloses document @x@ in parenthesis, \"(\"
-- and \")\".
parens :: Doc -> Doc
parens = enclose lparen rparen
-- | Document @(angles x)@ encloses document @x@ in angles, \"\<\" and
-- \"\>\".
angles :: Doc -> Doc
angles = enclose langle rangle
-- | Document @(brackets x)@ encloses document @x@ in square brackets,
-- \"[\" and \"]\".
brackets :: Doc -> Doc
brackets = enclose lbracket rbracket
-- | The document @(enclose l r x)@ encloses document @x@ between
-- documents @l@ and @r@ using @(\<\>)@.
enclose :: Doc -> Doc -> Doc -> Doc
enclose l r x = l <> x <> r
char :: Char -> Doc
char x = text [x]
-- | The document @lparen@ contains a left parenthesis, \"(\".
lparen :: Doc
lparen = char '('
-- | The document @rparen@ contains a right parenthesis, \")\".
rparen :: Doc
rparen = char ')'
-- | The document @langle@ contains a left angle, \"\<\".
langle :: Doc
langle = char '<'
-- | The document @rangle@ contains a right angle, \">\".
rangle :: Doc
rangle = char '>'
-- | The document @lbrace@ contains a left brace, \"{\".
lbrace :: Doc
lbrace = char '{'
-- | The document @rbrace@ contains a right brace, \"}\".
rbrace :: Doc
rbrace = char '}'
-- | The document @lbracket@ contains a left square bracket, \"[\".
lbracket :: Doc
lbracket = char '['
-- | The document @rbracket@ contains a right square bracket, \"]\".
rbracket :: Doc
rbracket = char ']'
-- | The document @squote@ contains a single quote, \"'\".
squote :: Doc
squote = char '\''
-- | The document @dquote@ contains a double quote, '\"'.
dquote :: Doc
dquote = char '"'
-- | The document @semi@ contains a semi colon, \";\".
semi :: Doc
semi = char ';'
-- | The document @colon@ contains a colon, \":\".
colon :: Doc
colon = char ':'
-- | The document @comma@ contains a comma, \",\".
comma :: Doc
comma = char ','
-- | The document @dot@ contains a single dot, \".\".
dot :: Doc
dot = char '.'
-- | The document @backslash@ contains a back slash, \"\\\".
backslash :: Doc
backslash = char '\\'
-- | The document @equals@ contains an equal sign, \"=\".
equals :: Doc
equals = char '='
-----------------------------------------------------------
-- Combinators for prelude types
-----------------------------------------------------------
-- string is like "text" but replaces '\n' by "line"
-- | The document @(string s)@ concatenates all characters in @s@
-- using @line@ for newline characters and @char@ for all other
-- characters. It is used instead of 'text' whenever the text contains
-- newline characters.
string :: String -> Doc
string = vcat . map text . lines
bool :: Bool -> Doc
bool b = text (show b)
-- | The document @(int i)@ shows the literal integer @i@ using
-- 'text'.
int :: Int -> Doc
int i = text (show i)
-- | The document @(integer i)@ shows the literal integer @i@ using
-- 'text'.
integer :: Integer -> Doc
integer i = text (show i)
-- | The document @(float f)@ shows the literal float @f@ using
-- 'text'.
float :: Float -> Doc
float f = text (show f)
-- | The document @(double d)@ shows the literal double @d@ using
-- 'text'.
double :: Double -> Doc
double d = text (show d)
-- | The document @(rational r)@ shows the literal rational @r@ using
-- 'text'.
rational :: Rational -> Doc
rational r = text (show r)
-- | The hang combinator implements hanging indentation. The document
-- @(hang i x)@ renders document @x@ with a nesting level set to the
-- current column plus @i@. The following example uses hanging
-- indentation for some text:
--
-- > test = hang 4 (fillSep (map text
-- > (words "the hang combinator indents these words !")))
--
-- Which lays out on a page with a width of 20 characters as:
--
-- @
-- the hang combinator
-- indents these
-- words !
-- @
--
-- The @hang@ combinator is implemented as:
--
-- > hang i x = align (nest i x)
hang :: Int -> Doc -> Doc -> Doc
hang n x y = (x <+> y) <|> (x $$ nest' n y)
-- | The document @(nest i x)@ renders document @x@ with the current
-- indentation level increased by i (See also 'hang', 'align' and
-- 'indent').
--
-- > nest 2 (text "hello" <$$$> text "world") <$$$> text "!"
--
-- outputs as:
--
-- @
-- hello
-- world
-- !
-- @
space = text " "
nest' n x = spaces n <> x
spaces :: Int -> Doc
spaces n = text $ replicate n ' '
($$) :: Doc -> Doc -> Doc
a $$ b = flush a <> b