-- Compiler Toolkit: pretty-printer combinators -- -- Author : Manuel M. T. Chakravarty -- Created: 16 February 95 -- -- Copyright (c) [1995..2000] Manuel M. T. Chakravarty -- -- This file is free software; you can redistribute it and/or modify -- it under the terms of the GNU General Public License as published by -- the Free Software Foundation; either version 2 of the License, or -- (at your option) any later version. -- -- This file is distributed in the hope that it will be useful, -- but WITHOUT ANY WARRANTY; without even the implied warranty of -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -- GNU General Public License for more details. -- --- DESCRIPTION --------------------------------------------------------------- -- -- This module provides combinators for pretty-printing, following the ideas -- in ``Pretty-printing: An Exercise in Functional Programming (DRAFT)'' by -- John Hughes. Subsequently, partially brought in line with Simon Peyton -- Jones' version of John Hughes' combinators. -- --- DOCU ---------------------------------------------------------------------- -- -- language: Haskell 98 -- -- * In a revision of the module, the names of the exported functions where -- brought in line with SimonPJs variant. The old names are still exported -- as to maintain compatibility to older code. They will disappear -- somewhere down the road. -- -- * The type of `fullRender' is different from the one in SimonPJ's variant. -- -- * `toDoc' is not supported by SimonPJ's variant. -- -- * The combinators `($+$)', `fcat', and `fsep' are not supported. -- --- TODO ---------------------------------------------------------------------- -- -- * currently `$|$' imposes a n^2 cost when building a text from top to -- bottom -- module Text.CTK.Pretty ( Doc, -- instance Show empty, isEmpty, char, text, nest, ($$), (<>), cat, sep, fullRender, -- -- derived combinators -- semi, comma, colon, dot, space, equals, lparen, rparen, lbrack, rbrack, lbrace, rbrace, toDoc, int, integer, float, double, rational, parens, brackets, braces, quotes, doubleQuotes, (<+>), hcat, hsep, vcat, hang, punctuate, render, -- -- pretty-printing type class & precedences -- Pretty(pretty, prettyPrec), usedWhen, Assoc(..), infixOp, -- -- the following routines are part of the legacy interface that should not -- be used anymore - it will disappear in due course -- textDoc, nestDoc, ($|$), (<^>), sepDocs, bestDoc, -- -- *** for debugging ONLY *** -- dumpDoc ) where infixl 6 <>, <+> -- vertical composition infixl 5 $$ -- horizontal composition -- default parameters -- ------------------ dftWidth :: Int dftWidth = 79 dftRibbonRatio :: Float dftRibbonRatio = 1.5 -- representation of documents -- --------------------------- -- a document is a compact representation (tree shaped) of a set of layouts -- for a given text (EXPORTED ABSTRACTLY) -- data Doc = Nest Int [DocAlt] -- set of layouts, indented as given data DocAlt = Text String -- one row | TextAbove String Doc -- row of text above the remaining doc -- render with defaults -- instance Show Doc where showsPrec _ = showString . render -- empty document (EXPORTED) -- empty :: Doc empty = Nest 0 [] -- test for emptiness (EXPORTED) -- isEmpty :: Doc -> Bool isEmpty (Nest _ []) = True isEmpty _ = False -- single character (EXPORTED) -- char :: Char -> Doc char c = text [c] -- single line of text (EXPORTED) -- text :: String -> Doc text s = Nest 0 [Text s] -- increase nesting of given document (EXPORTED) -- nest :: Int -> Doc -> Doc nest k (Nest m alts) = Nest (k + m) alts -- vertical composition of documents (EXPORTED) -- ($$) :: Doc -> Doc -> Doc (Nest _ [] ) $$ doc = doc (Nest m alts) $$ doc = Nest m [below a | a <- alts] where below :: DocAlt -> DocAlt below (Text s) = let doc' = nestDoc (-m) doc in TextAbove s doc' below (TextAbove s rest) = let doc' = rest $$ nestDoc (-m) doc in TextAbove s doc' -- horizontal composition of documents (EXPORTED) -- (<>) :: Doc -> Doc -> Doc (Nest _ [] ) <> doc = doc doc <> (Nest _ [] ) = doc (Nest m alts) <> doc = Nest m (concat [nextTo a | a <- alts]) where nextTo :: DocAlt -> [DocAlt] nextTo (Text s) = let Nest _ bs = doc in [s `inFrontOf` b | b <- bs] nextTo (TextAbove s rest) = [TextAbove s (rest <> doc)] inFrontOf :: String -> DocAlt -> DocAlt s `inFrontOf` (Text t) = Text (s ++ t) s `inFrontOf` (TextAbove t doc') = let l = length s in TextAbove (s ++ t) (nestDoc l doc') -- given a list of sub-documents, generate a composite document where the -- sub-documents are placed next to each other (EXPORTED) -- -- * when generating a layout a horizontal layout is only chosen -- when the given collection of sub-documents fits on a single line -- cat :: [Doc] -> Doc cat docs = catsep (<>) docs -- given a list of sub-documents, generate a composite document where the -- sub-documents are placed next to each other with some seperation between -- each of them (EXPORTED) -- -- * when generating a layout a horizontal layout is only chosen -- when the given collection of sub-documents fits on a single line -- sep :: [Doc] -> Doc sep docs = catsep (<+>) docs -- generalise `cat' and `sep' -- catsep :: (Doc -> Doc -> Doc) -> [Doc] -> Doc catsep _ [] = textDoc "" catsep hcomb docs = fitunion (foldr hcomb empty docs) (foldr ($$) empty docs) where -- -- given two documents, where the first one is a horizontal -- composition, we only choose a single line alternative (if at -- all present) from the first document -- fitunion :: Doc -> Doc -> Doc fitunion (Nest m (Text s : _)) (Nest _ alts) = Nest m (Text s : alts) fitunion _ doc = doc -- select the best layout from a document and return it in string form -- (EXPORTED) -- -- * given are the overall width and the ribbon ration, ie, the number of -- times the ribbon fits into a line (the ribbon is the number of -- characters on a line excluding leading and trailing white spaces) -- fullRender :: Int -> Float -> Doc -> String fullRender width ribbonRatio = let ribbon = round (fromIntegral width / ribbonRatio) in dropWhile (== '\n') . nestbest 0 width ribbon where -- -- like `best', but with explicit nesting -- nestbest :: Int -> Int -> Int -> Doc -> String nestbest k w r (Nest _ [] ) = "" nestbest k w r (Nest m alts) = case foldr1 (choose (w - m) r) alts of Text s -> indent (k + m) s TextAbove s bs -> indent (k + m) s ++ nestbest (k + m) (w - m) r bs -- -- indent the given string by the given amount -- indent :: Int -> String -> String indent k s = "\n" ++ copy k ' ' ++ s where copy :: Int -> a -> [a] copy n = take n . repeat -- given the remaining width and ribbon together with two possible -- documents, choose the first one if its first line is nice; otherwise, -- take the second -- choose :: Int -> Int -> DocAlt -> DocAlt -> DocAlt choose w r alts1 alts2 = if (nice w r (firstline alts1)) then alts1 else alts2 where firstline (Text s) = s firstline (TextAbove s _) = s -- given remaining width and ribbon width decide whether a line -- is nice or not -- nice :: Int -> Int -> String -> Bool nice w r s = (l <= w) && (l <= r) where l = length s -- derived combinators -- ------------------- -- punctuation characters (EXPORTED) -- semi, comma, colon, dot :: Doc semi = char ';' comma = char ',' colon = char ':' dot = char '.' -- separators (EXPORTED) -- space, equals :: Doc space = char ' ' equals = char '=' -- round parenthesis (EXPORTED) -- lparen, rparen :: Doc lparen = char '(' rparen = char ')' -- square brackets (EXPORTED) -- lbrack, rbrack :: Doc lbrack = char '[' rbrack = char ']' -- curly braces (EXPORTED) -- lbrace, rbrace :: Doc lbrace = char '{' rbrace = char '}' -- any value that has a textual representation (EXPORTED) -- toDoc :: Show a => a -> Doc toDoc = text . show -- ints (EXPORTED) -- -- * these are only for compatibility with SimonPJ's `Pretty' module as `toDoc' -- is more general -- int :: Int -> Doc int = toDoc integer :: Integer -> Doc integer = toDoc float :: Float -> Doc float = toDoc double :: Double -> Doc double = toDoc rational :: Rational -> Doc rational = toDoc -- wrap a document into various forms of brackets -- parens, brackets, braces :: Doc -> Doc parens doc = lparen <> doc <> rparen brackets doc = lbrack <> doc <> rbrack braces doc = lbrace <> doc <> rbrace -- wrap a document into quotes -- quotes, doubleQuotes :: Doc -> Doc quotes doc = char '`' <> doc <> char '\'' doubleQuotes doc = char '"' <> doc <> char '"' -- horizontal composition including a space if none of the documents is empty -- (EXPORTED) -- (<+>) :: Doc -> Doc -> Doc d1 <+> d2 | isEmpty d1 = d2 | isEmpty d2 = d1 | otherwise = d1 <> space <> d2 -- list version of horizontal composition (EXPORTED) -- hcat :: [Doc] -> Doc hcat = foldr (<>) empty -- list version of horizontal composition including a space (EXPORTED) -- hsep :: [Doc] -> Doc hsep = foldr (<+>) empty -- list version of vertical composition (EXPORTED) -- vcat :: [Doc] -> Doc vcat = foldr ($$) empty -- hang the second document of the first, where the second one is indented -- (EXPORTED) -- hang :: Doc -> Int -> Doc -> Doc hang doc1 n doc2 = sep [doc1, nest n doc2] -- add a punctuation document to every document in a list, but the last -- (EXPORTED) -- punctuate :: Doc -> [Doc] -> [Doc] punctuate _ [] = [] punctuate p ds = map (<> p) (init ds) ++ [last ds] -- render a document using the default settings -- render :: Doc -> String render = fullRender dftWidth dftRibbonRatio -- type class and precedence -- ------------------------- -- overloaded pretty-printing function (EXPORTED) -- class Pretty a where pretty :: a -> Doc prettyPrec :: Int -> a -> Doc pretty = prettyPrec 0 prettyPrec _ = pretty -- useful to keep the interface simple and general -- instance Pretty Doc where pretty = id -- conditionally apply a document transformer (EXPORTED) -- -- * typically a function like `parens' is applied when the precedences require -- this -- usedWhen :: (Doc -> Doc) -> Bool -> Doc -> Doc usedWhen wrap c doc | c = wrap doc | otherwise = doc -- associativity of an infix operator (EXPORTED) -- data Assoc = LeftAssoc | RightAssoc | NoAssoc deriving (Eq) -- pretty print an infix operator given its precedence, lexeme, and its two -- arguments (EXPORTED) -- infixOp :: (Pretty a, Pretty b) => Assoc -- associativity of operator -> Int -- precedence of operator -> String -- lexeme of operator -> a -- left argument -> b -- right argument -> Int -- precedence of context -> Doc infixOp assoc opp lexeme arg1 arg2 p = parens `usedWhen` (p > opp) $ hsep [ prettyPrec leftOpp arg1, text lexeme, prettyPrec rightOpp arg2 ] where leftOpp = if (assoc == RightAssoc) then opp + 1 else opp rightOpp = if (assoc == LeftAssoc ) then opp + 1 else opp -- the legacy interface (this is only kept for compatibility) -- -------------------- infixr 1 $|$ -- vertical composition infixr 1 <^> -- horizontal composition textDoc :: String -> Doc textDoc = text nestDoc :: Int -> Doc -> Doc nestDoc = nest ($|$) :: Doc -> Doc -> Doc ($|$) = ($$) (<^>) :: Doc -> Doc -> Doc (<^>) = (<>) sepDocs :: [Doc] -> Doc sepDocs = sep bestDoc :: Int -> Int -> Doc -> String bestDoc width ribbon = fullRender width (fromIntegral width / fromIntegral ribbon) -- debugging support -- ----------------- dumpDoc :: Doc -> String dumpDoc (Nest _ [] ) = "" dumpDoc (Nest m alts) = unlines . map (++ "\n--") . map outline $ alts where outline (Text str ) = str outline (TextAbove str _) = str ++ "\n..."