packages feed

darcs-cabalized-2.0.2: src/Printer.lhs

A Document is at heart ShowS from the prelude
\htmladdnormallink{http://www.haskell.org/onlinereport/standard-prelude.html#\$tShowS}

Essentially, if you give a Doc a string it'll print out whatever it
wants followed by that string. So \verb!(text "foo")! makes the Doc that
prints \verb!"foo"! followed by its argument. The combinator names are taken
from Text.PrettyPrint.HughesPJ, although the behaviour of the two libraries is
slightly different.

The advantage of Printer over simple string appending/concatenating is
that the appends end up associating to the right, e.g.:

\begin{verbatim}
  (text "foo" <> text "bar") <> (text "baz" <> text "quux") ""
= \s -> (text "foo" <> text "bar") ((text "baz" <> text "quux") s) ""
= (text "foo" <> text "bar") ((text "baz" <> text "quux") "")
= (\s -> (text "foo") (text "bar" s)) ((text "baz" <> text "quux") "")
= text "foo" (text "bar" ((text "baz" <> text "quux") ""))
= (\s -> "foo" ++ s) (text "bar" ((text "baz" <> text "quux") ""))
= "foo" ++ (text "bar" ((text "baz" <> text "quux") ""))
= "foo" ++ ("bar" ++ ((text "baz" <> text "quux") ""))
= "foo" ++ ("bar" ++ ((\s -> text "baz" (text "quux" s)) ""))
= "foo" ++ ("bar" ++ (text "baz" (text "quux" "")))
= "foo" ++ ("bar" ++ ("baz" ++ (text "quux" "")))
= "foo" ++ ("bar" ++ ("baz" ++ ("quux" ++ "")))
\end{verbatim}

The Empty alternative comes in because you want
\begin{verbatim}
    text "a" $$ vcat xs $$ text "b"
\end{verbatim}
(\verb!$$! means ``above'', vcat is the list version of \verb!$$!) to be
\verb!"a\nb"! when \verb!xs!  is \verb![]!, but without the concept of an
Empty Document each \verb!$$! would add a \verb!'\n'! and you'd end up with
\verb!"a\n\nb"!. Note that \verb!Empty /= text ""! (the latter would cause two
\verb!'\n'!s).

This code was made generic in the element type by Juliusz Chroboczek.
\begin{code}
module Printer (Printable(..), Doc(Doc,unDoc), Printers, Printers'(..), Printer, Color(..),
                hPutDoc,     hPutDocLn,     putDoc,     putDocLn,
                hPutDocWith, hPutDocLnWith, putDocWith, putDocLnWith,
                renderString, renderStringWith, renderPS, renderPSWith,
                renderPSs, renderPSsWith, lineColor,
                prefix, colorText, invisibleText, hiddenText, hiddenPrefix, userchunk, text,
                printable, wrap_text,
                blueText, redText, greenText, magentaText, cyanText,
                unsafeText, unsafeBoth, unsafeBothText, unsafeChar,
                invisiblePS, packedString, unsafePackedString, userchunkPS,
                simplePrinters, invisiblePrinter, simplePrinter,
                doc, empty, (<>), (<?>), (<+>), ($$), vcat, vsep, hcat,
                minus, newline, plus, space, backslash, lparen, rparen,
                parens,
                errorDoc,
               ) where

import Control.Monad.Reader (Reader, runReader, ask, asks, local)
import Data.List (intersperse)
import System.IO (Handle, stdout, hPutStr)
import FastPackedString (PackedString, packString, hPutPS, unpackPS, concatPS)

data Printable = S !String
               | PS !PackedString
               | Both !String !PackedString

space_p, newline_p :: Printable
space_p   = Both " "  (packString " ")
newline_p = S "\n"

space, newline, plus, minus, backslash :: Doc
space     = unsafeBoth " "  (packString " ")
newline   = unsafeChar '\n'
minus     = unsafeBoth "-"  (packString "-")
plus      = unsafeBoth "+"  (packString "+")
backslash = unsafeBoth "\\" (packString "\\")

lparen, rparen :: Doc
lparen = unsafeBoth  "(" (packString "(")
rparen = unsafeBoth ")" (packString ")")

parens :: Doc -> Doc
parens d = lparen <> d <> rparen

errorDoc :: Doc -> a
errorDoc = error . renderStringWith simplePrinters'

putDocWith, putDocLnWith :: Printers -> Doc -> IO ()
putDocWith prs = hPutDocWith prs stdout
putDocLnWith prs = hPutDocLnWith prs stdout

putDoc, putDocLn :: Doc -> IO ()
putDoc = hPutDoc stdout
putDocLn = hPutDocLn stdout

hPutDocWith, hPutDocLnWith :: Printers -> Handle -> Doc -> IO ()
hPutDocWith prs h d = hPrintPrintables h (renderWith (prs h) d)
hPutDocLnWith prs h d = hPutDocWith prs h (d <?> newline)

hPutDoc, hPutDocLn :: Handle -> Doc -> IO ()
hPutDoc = hPutDocWith simplePrinters
hPutDocLn = hPutDocLnWith simplePrinters

hPrintPrintables :: Handle -> [Printable] -> IO ()
hPrintPrintables h = mapM_ (hPrintPrintable h)

hPrintPrintable :: Handle -> Printable -> IO ()
hPrintPrintable h (S ps) = hPutStr h ps
hPrintPrintable h (PS ps) = hPutPS h ps
hPrintPrintable h (Both _ ps) = hPutPS h ps

newtype Doc = Doc { unDoc :: Reader St Document }

data St = St { printers :: !Printers', current_prefix :: !DocumentInternals }
type Printers = Handle -> Printers'
data Printers' = Printers {colorP :: !(Color -> Printer),
                           invisibleP :: !Printer,
                           hiddenP :: !Printer,
                           userchunkP :: !Printer,
                           defP :: !Printer,
                           lineColorT :: !(Color -> Doc -> Doc),
                           lineColorS :: !DocumentInternals
                          }
type Printer = Printable -> Reader St Document

data Color = Blue | Red | Green | Cyan | Magenta

type DocumentInternals = [Printable] -> [Printable]
data Document = Document DocumentInternals
              | Empty

renderString :: Doc -> String
renderString = renderStringWith simplePrinters'

renderStringWith :: Printers' -> Doc -> String
renderStringWith prs d = concatMap toString $ renderWith prs d
    where toString (S s) = s
          toString (PS ps) = unpackPS ps
          toString (Both s _) = s

renderPS :: Doc -> PackedString
renderPS = renderPSWith simplePrinters'

renderPSs :: Doc -> [PackedString]
renderPSs = renderPSsWith simplePrinters'

renderPSWith :: Printers' -> Doc -> PackedString
renderPSWith prs d = concatPS $ renderPSsWith prs d

renderPSsWith :: Printers' -> Doc -> [PackedString]
renderPSsWith prs d = map toPS $ renderWith prs d
    where toPS (S s) = packString s
          toPS (PS ps) = ps
          toPS (Both _ ps) = ps

renderWith :: Printers' -> Doc -> [Printable]
renderWith ps (Doc d) = case runReader d (init_state ps) of
                        Empty -> []
                        Document f -> f []

init_state :: Printers' -> St
init_state prs = St { printers = prs, current_prefix = id }

prefix :: String -> Doc -> Doc
prefix s (Doc d) =
    Doc $ local (\st -> st { current_prefix = current_prefix st . (p:) })
                   (do d' <- d
                       case d' of
                           Document d'' -> return $ Document $ (p:) . d''
                           Empty -> return Empty)
    where p = S s

lineColor :: Color -> Doc -> Doc
lineColor c d =
    Doc $ do pr <- asks printers
             unDoc $ lineColorT pr c d

hiddenPrefix :: String -> Doc -> Doc
hiddenPrefix s (Doc d) =
    Doc $ do pr <- asks printers
             let p = S (renderStringWith pr $ hiddenText s)
             local (\st -> st { current_prefix = current_prefix st . (p:) })
                       (do d' <- d
                           case d' of
                             Document d'' -> return $ Document $ (p:) . d''
                             Empty -> return Empty)

unsafeBoth :: String -> PackedString -> Doc
unsafeBoth s ps = Doc $ simplePrinter (Both s ps)

unsafeBothText :: String -> Doc
unsafeBothText s = Doc $ simplePrinter (Both s (packString s))

packedString, unsafePackedString, invisiblePS, userchunkPS :: PackedString -> Doc
packedString = printable . PS
unsafePackedString = Doc . simplePrinter . PS
invisiblePS = invisiblePrintable . PS
userchunkPS = userchunkPrintable . PS

unsafeChar :: Char -> Doc
unsafeChar = unsafeText . return

text, unsafeText, invisibleText, hiddenText, userchunk, blueText, redText, greenText, magentaText, cyanText :: String -> Doc
text = printable . S
unsafeText = Doc . simplePrinter . S
invisibleText = invisiblePrintable . S
hiddenText = hiddenPrintable . S
userchunk = userchunkPrintable . S
blueText = colorText Blue
redText = colorText Red
greenText = colorText Green
magentaText = colorText Magenta
cyanText = colorText Cyan

colorText :: Color -> String -> Doc
colorText c = mkColorPrintable c . S

wrap_text :: Int -> String -> Doc
wrap_text n s =
    vcat $ map text $ reverse $ "": (foldl add_to_line [] $ words s)
  where add_to_line [] a = [a]
        add_to_line ("":d) a = (a:d)
        add_to_line (l:ls) new | length l + length new > n = new:l:ls
        add_to_line (l:ls) new = (l ++ " " ++ new):ls

printable, invisiblePrintable, hiddenPrintable, userchunkPrintable :: Printable -> Doc
printable x = Doc $ do st <- ask
                       defP (printers st) x
mkColorPrintable :: Color -> Printable -> Doc
mkColorPrintable c x = Doc $ do st <- ask
                                colorP (printers st) c x
invisiblePrintable x = Doc $ do st <- ask
                                invisibleP (printers st) x
hiddenPrintable x = Doc $ do st <- ask
                             hiddenP (printers st) x
userchunkPrintable x = Doc $ do st <- ask
                                userchunkP (printers st) x

simplePrinters :: Printers
simplePrinters _ = simplePrinters'

simplePrinters' :: Printers'
simplePrinters'  = Printers { colorP = const simplePrinter,
                              invisibleP = simplePrinter,
                              hiddenP = invisiblePrinter,
                              userchunkP = simplePrinter,
                              defP = simplePrinter,
                              lineColorT = const id,
                              lineColorS = id
                            }

simplePrinter :: Printer
invisiblePrinter :: Printer
simplePrinter x = unDoc $ doc (\s -> x:s)
invisiblePrinter _ = unDoc empty

infixr 6 <>
infixr 6 <+>
infixr 5 $$

empty :: Doc
empty = Doc $ return Empty
doc :: ([Printable] -> [Printable]) -> Doc
doc f = Doc $ return $ Document f

(<>), (<?>), (<+>), ($$) :: Doc -> Doc -> Doc
-- a then b
Doc a <> Doc b =
   Doc $ do ad <- a
            case ad of
                Empty -> b
                Document af ->
                    do bd <- b
                       return $ Document (\s -> af $ case bd of
                                                         Empty -> s
                                                         Document bf -> bf s)

-- empty if a empty, else a then b
Doc a <?> Doc b =
    Doc $ do ad <- a
             case ad of
                 Empty -> return Empty
                 Document af ->
                     do bd <- b
                        return $ Document (\s -> af $ case bd of
                                                          Empty -> s
                                                          Document bf -> bf s)

-- a then space then b
Doc a <+> Doc b =
    Doc $ do ad <- a
             case ad of
                 Empty -> b
                 Document af ->
                     do bd <- b
                        return $ Document (\s -> af $ case bd of
                                                          Empty -> s
                                                          Document bf ->
                                                              space_p:bf s)

-- a above b
Doc a $$ Doc b =
   Doc $ do ad <- a
            case ad of
                Empty -> b
                Document af ->
                    do bd <- b
                       st <- ask
                       let pf = current_prefix st
                           sf = lineColorS $ printers st
                       return $ Document (\s -> af
                                              $ case bd of
                                                    Empty -> s
                                                    Document bf ->
                                                        sf (newline_p:pf (bf s)))

vcat :: [Doc] -> Doc
vcat [] = empty
vcat ds = foldr1 ($$) ds

vsep :: [Doc] -> Doc
vsep [] = empty
vsep ds = foldr1 ($$) $ intersperse (text "") ds

hcat :: [Doc] -> Doc
hcat [] = empty
hcat ds = foldr1 (<>) ds
\end{code}