[project @ 1997-05-18 04:52:59 by sof]
authorsof <unknown>
Sun, 18 May 1997 04:52:59 +0000 (04:52 +0000)
committersof <unknown>
Sun, 18 May 1997 04:52:59 +0000 (04:52 +0000)
New glorious PP machinery

ghc/compiler/utils/Pretty.lhs

index d610c36..3e34f5f 100644 (file)
-%
-% (c) The GRASP/AQUA Project, Glasgow University, 1992-1996
-%
-\section[Pretty]{Pretty-printing data type}
+*********************************************************************************
+*                                                                              *
+*      John Hughes's and Simon Peyton Jones's Pretty Printer Combinators       *
+*                                                                              *
+*              based on "The Design of a Pretty-printing Library"              *
+*              in Advanced Functional Programming,                             *
+*              Johan Jeuring and Erik Meijer (eds), LNCS 925                   *
+*              http://www.cs.chalmers.se/~rjmh/Papers/pretty.ps                *
+*                                                                              *
+*              Heavily modified by Simon Peyton Jones, Dec 96                  *
+*                                                                              *
+*********************************************************************************
+
+Version 2.0    24 April 1997
+  * Made empty into a left unit for <> as well as a right unit;
+    it is also now true that
+       nest k empty = empty
+    which wasn't true before.
+
+  * Fixed an obscure bug in sep that occassionally gave very wierd behaviour
+
+  * Added $+$
+
+  * Corrected and tidied up the laws and invariants
+
+======================================================================
+Relative to John's original paper, there are the following new features:
+
+1.  There's an empty document, "empty".  It's a left and right unit for 
+    both <> and $$, and anywhere in the argument list for
+    sep, hcat, hsep, vcat, fcat etc.
+
+    It is Really Useful in practice.
+
+2.  There is a paragraph-fill combinator, fsep, that's much like sep,
+    only it keeps fitting things on one line until itc can't fit any more.
+
+3.  Some random useful extra combinators are provided.  
+       <+> puts its arguments beside each other with a space between them,
+           unless either argument is empty in which case it returns the other
+
+
+       hcat is a list version of <>
+       hsep is a list version of <+>
+       vcat is a list version of $$
+
+       sep (separate) is either like hsep or like vcat, depending on what fits
+
+       cat  is behaves like sep,  but it uses <> for horizontal conposition
+       fcat is behaves like fsep, but it uses <> for horizontal conposition
+
+       These new ones do the obvious things:
+               char, semi, comma, colon, space,
+               parens, brackets, braces, 
+               quotes, doubleQuotes
+       
+4.     The "above" combinator, $$, now overlaps its two arguments if the
+       last line of the top argument stops before the first line of the second begins.
+       For example:  text "hi" $$ nest 5 "there"
+       lays out as
+                       hi   there
+       rather than
+                       hi
+                            there
+
+       There are two places this is really useful
+
+       a) When making labelled blocks, like this:
+               Left ->   code for left
+               Right ->  code for right
+               LongLongLongLabel ->
+                         code for longlonglonglabel
+          The block is on the same line as the label if the label is
+          short, but on the next line otherwise.
+
+       b) When laying out lists like this:
+               [ first
+               , second
+               , third
+               ]
+          which some people like.  But if the list fits on one line
+          you want [first, second, third].  You can't do this with
+          John's original combinators, but it's quite easy with the
+          new $$.
+
+       The combinator $+$ gives the original "never-overlap" behaviour.
+
+5.     Several different renderers are provided:
+               * a standard one
+               * one that uses cut-marks to avoid deeply-nested documents 
+                       simply piling up in the right-hand margin
+               * one that ignores indentation (fewer chars output; good for machines)
+               * one that ignores indentation and newlines (ditto, only more so)
+
+6.     Numerous implementation tidy-ups
+       Use of unboxed data types to speed up the implementation
+
+
 
 \begin{code}
-#if defined(COMPILING_GHC)
-# include "HsVersions.h"
-#else
-# define FAST_STRING String
-# define _LENGTH_    length
-#endif
+#include "HsVersions.h"
 
 module Pretty (
+       Doc,            -- Abstract
+       Mode(..), TextDetails(..),
 
-#if defined(COMPILING_GHC)
-       SYN_IE(Pretty),
-       prettyToUn,
-#else
-       Pretty,
+       empty, nest,
+
+       text, char, ptext,
+       int, integer, float, double, rational,
+       parens, brackets, braces, quotes, doubleQuotes,
+       semi, comma, colon, space, equals,
+       lparen, rparen, lbrack, rbrack, lbrace, rbrace,
+
+       (<>), (<+>), hcat, hsep, 
+       ($$), ($+$), vcat, 
+       sep, cat, 
+       fsep, fcat, 
+
+       hang, punctuate,
+       
+--     renderStyle,            -- Haskell 1.3 only
+       render, fullRender
+  ) where
+
+#include "HsVersions.h"
+#if defined(__GLASGOW_HASKELL__)
+
+import FastString
+IMP_Ubiq()
+
+#if __GLASGOW_HASKELL__ >= 202
+import GHC
+import PrelBase
 #endif
-       ppNil, ppStr, ppPStr, ppChar, ppInt, ppInteger,
-       ppFloat, ppDouble,
-#if __GLASGOW_HASKELL__
-       -- may be able to *replace* ppDouble
-       ppRational,
 #endif
-       ppSP, pp'SP, ppLbrack, ppRbrack, ppLparen, ppRparen,
-       ppSemi, ppComma, ppEquals,
-       ppBracket, ppParens, ppQuote, ppCurlies,
 
-       ppCat, ppBeside, ppBesides, ppAbove, ppAboves,
-       ppNest, ppSep, ppHang, ppInterleave, ppIntersperse,
-       ppShow, speakNth,
 
-#if defined(COMPILING_GHC)
-       ppPutStr,
-#endif
+import Util    ( assertPanic )
+
+infixl 6 <> 
+infixl 6 <+>
+infixl 5 $$, $+$
+\end{code}
+
+
 
-       -- abstract type, to complete the interface...
-       PrettyRep(..), Delay
-   ) where
+*********************************************************
+*                                                      *
+\subsection{CPP magic so that we can compile with both GHC and Hugs}
+*                                                      *
+*********************************************************
 
-#if defined(COMPILING_GHC)
+The library uses unboxed types to get a bit more speed, but these CPP macros
+allow you to use either GHC or Hugs.  To get GHC, just set the CPP variable
+       __GLASGOW_HASKELL__
 
-CHK_Ubiq() -- debugging consistency check
-IMPORT_1_3(Ratio)
-IMPORT_1_3(IO)
+\begin{code}
+
+#if defined(__GLASGOW_HASKELL__)
+
+
+-- Glasgow Haskell
+
+-- Disable ASSERT checks; they are expensive!
+#define LOCAL_ASSERT(x)
+
+#define INT    Int#
+#define MINUS  -#
+#define NEGATE  negateInt#
+#define PLUS   +#
+#define GR     >#
+#define GREQ   >=#
+#define LT     <#
+#define DIV    `quotInt#`
+
+
+#if __GLASGOW_HASKELL__ >= 202
+                       -- Haskell 1.3 stuff
+#define SHOW   Show
+#define MAXINT maxBound
 
-import Unpretty                ( SYN_IE(Unpretty) )
 #else
-import Ratio
+                       -- Haskell 1.2 stuff
+#define SHOW   Text
+#define MAXINT maxInt
+#endif
+
+
+#else
+
+-- Standard Haskell
+
+#define LOCAL_ASSERT(x)
+
+#define INT    Int
+#define IBOX(x)        x
+#define MINUS  -
+#define NEGATE  negate
+#define PLUS   +
+#define GR     >
+#define GREQ   >=
+#define LT     <
+#define DIV    `quot`
+#define ILIT(x) x
+
+#define SHOW   Show
+#define MAXINT maxBound
+
 #endif
 
-import CharSeq
 \end{code}
 
-Based on John Hughes's pretty-printing library.  Loosely.  Very
-loosely.
 
-%************************************************
-%*                                             *
-       \subsection{The interface}
-%*                                             *
-%************************************************
+*********************************************************
+*                                                      *
+\subsection{The interface}
+*                                                      *
+*********************************************************
+
+The primitive @Doc@ values
 
 \begin{code}
-ppNil          :: Pretty
-ppSP, pp'SP, ppLbrack, ppRbrack, ppLparen, ppRparen, ppSemi, ppComma, ppEquals :: Pretty
-
-ppStr          :: [Char] -> Pretty
-ppPStr         :: FAST_STRING -> Pretty
-ppChar         :: Char    -> Pretty
-ppInt          :: Int     -> Pretty
-ppInteger      :: Integer -> Pretty
-ppDouble       :: Double  -> Pretty
-ppFloat                :: Float   -> Pretty
-ppRational     :: Rational -> Pretty
-
-ppBracket      :: Pretty -> Pretty -- put brackets around it
-ppParens       :: Pretty -> Pretty -- put parens   around it
-
-ppBeside       :: Pretty -> Pretty -> Pretty
-ppBesides      :: [Pretty] -> Pretty
-ppBesideSP     :: Pretty -> Pretty -> Pretty
-ppCat          :: [Pretty] -> Pretty           -- i.e., ppBesidesSP
-
-ppAbove                :: Pretty -> Pretty -> Pretty
-ppAboves       :: [Pretty] -> Pretty
-
-ppInterleave   :: Pretty -> [Pretty] -> Pretty
-ppIntersperse  :: Pretty -> [Pretty] -> Pretty -- no spaces between, no ppSep
-ppSep          :: [Pretty] -> Pretty
-ppHang         :: Pretty -> Int -> Pretty -> Pretty
-ppNest         :: Int -> Pretty -> Pretty
-
-ppShow         :: Int -> Pretty -> [Char]
-
-#if defined(COMPILING_GHC)
-ppPutStr       :: Handle -> Int -> Pretty -> IO ()
-#endif
+empty                            :: Doc
+text                     :: String -> Doc 
+char                     :: Char -> Doc
+
+semi, comma, colon, space, equals             :: Doc
+lparen, rparen, lbrack, rbrack, lbrace, rbrace :: Doc
+
+parens, brackets, braces  :: Doc -> Doc 
+quotes, doubleQuotes     :: Doc -> Doc
+
+int     :: Int -> Doc
+integer  :: Integer -> Doc
+float   :: Float -> Doc
+double  :: Double -> Doc
+rational :: Rational -> Doc
 \end{code}
 
-%************************************************
-%*                                             *
-       \subsection{The representation}
-%*                                             *
-%************************************************
+Combining @Doc@ values
 
 \begin{code}
-type Pretty = Int      -- The width to print in
-          -> Bool      -- True => vertical context
-          -> PrettyRep
+(<>)   :: Doc -> Doc -> Doc    -- Beside
+hcat   :: [Doc] -> Doc         -- List version of <>
+(<+>)  :: Doc -> Doc -> Doc    -- Beside, separated by space
+hsep   :: [Doc] -> Doc         -- List version of <+>
 
-data PrettyRep
-  = MkPrettyRep        CSeq    -- The text
-               (Delay Int) -- No of chars in last line
-               Bool    -- True if empty object
-               Bool    -- Fits on a single line in specified width
+($$)   :: Doc -> Doc -> Doc    -- Above; if there is no
+                               -- overlap it "dovetails" the two
+vcat   :: [Doc] -> Doc         -- List version of $$
 
-data Delay a = MkDelay a
+cat    :: [Doc] -> Doc         -- Either hcat or vcat
+sep    :: [Doc] -> Doc         -- Either hsep or vcat
+fcat   :: [Doc] -> Doc         -- ``Paragraph fill'' version of cat
+fsep   :: [Doc] -> Doc         -- ``Paragraph fill'' version of sep
+
+nest   :: Int -> Doc -> Doc    -- Nested
+\end{code}
 
-forceDel (MkDelay _) r = r
+GHC-specific ones.
 
-forceBool True  r = r
-forceBool False r = r
+\begin{code}
+hang :: Doc -> Int -> Doc -> Doc
+punctuate :: Doc -> [Doc] -> [Doc]     -- punctuate p [d1, ... dn] = [d1 <> p, d2 <> p, ... dn-1 <> p, dn]
+\end{code}
 
-forceInfo ll emp sl r = forceDel ll (forceBool emp (forceBool sl r))
+Displaying @Doc@ values. 
 
-ppShow width p
-  = case (p width False) of
-      MkPrettyRep seq ll emp sl -> cShow seq
+\begin{code}
+instance SHOW Doc where
+  showsPrec prec doc cont = showDoc doc cont
+
+render     :: Doc -> String            -- Uses default style
+fullRender :: Mode
+          -> Int                       -- Line length
+          -> Float                     -- Ribbons per line
+          -> (TextDetails -> a -> a)   -- What to do with text
+          -> a                         -- What to do at the end
+          -> Doc
+          -> a                         -- Result
+
+{-     When we start using 1.3 
+renderStyle  :: Style -> Doc -> String
+data Style = Style { lineLength     :: Int,    -- In chars
+                    ribbonsPerLine :: Float,   -- Ratio of ribbon length to line length
+                    mode :: Mode
+            }
+style :: Style         -- The default style
+style = Style { lineLength = 100, ribbonsPerLine = 2.5, mode = PageMode }
+-}
 
-#if defined(COMPILING_GHC)
-ppPutStr f width p
-  = case (p width False) of
-      MkPrettyRep seq ll emp sl -> cPutStr f seq
-#endif
+data Mode = PageMode           -- Normal 
+         | ZigZagMode          -- With zig-zag cuts
+         | LeftMode            -- No indentation, infinitely long lines
+         | OneLineMode         -- All on one line
 
-ppNil    width is_vert = MkPrettyRep cNil (MkDelay 0) True (width >= 0)
-                          -- Doesn't fit if width < 0, otherwise, ppNil
-                          -- will make ppBesides always return True.
-
-ppStr  s width is_vert = MkPrettyRep (cStr s) (MkDelay ls) False (width >= ls)
-                          where ls = length s
-ppPStr s width is_vert = MkPrettyRep (cPStr s) (MkDelay ls) False (width >= ls)
-                          where ls = _LENGTH_ s
-ppChar c width is_vert = MkPrettyRep (cCh c) (MkDelay 1) False (width >= 1)
-
-ppInt  n width is_vert = MkPrettyRep (cStr s) (MkDelay ls) False (width >= ls)
-                          where s = show n; ls = length s
-
-ppInteger n  = ppStr (show n)
-ppDouble  n  = ppStr (show n)
-ppFloat   n  = ppStr (show n)
-
-ppRational n = ppStr (show (fromRationalX n)) -- _showRational 30 n)
-
-ppSP     = ppChar ' '
-pp'SP    = ppStr ", "
-ppLbrack  = ppChar '['
-ppRbrack  = ppChar ']'
-ppLparen  = ppChar '('
-ppRparen  = ppChar ')'
-ppSemi    = ppChar ';'
-ppComma   = ppChar ','
-ppEquals  = ppChar '='
-
-ppBracket p = ppBeside ppLbrack (ppBeside p ppRbrack)
-ppParens  p = ppBeside ppLparen (ppBeside p ppRparen)
-ppCurlies p = ppBeside (ppChar '{') (ppBeside p (ppChar '}'))
-ppQuote   p = ppBeside (ppChar '`') (ppBeside p (ppChar '\''))
-
-ppInterleave sep ps = ppSep (pi ps)
-  where
-   pi []       = []
-   pi [x]      = [x]
-   pi (x:xs)   = (ppBeside x sep) : pi xs
 \end{code}
 
-ToDo: this could be better: main pt is: no extra spaces in between.
+
+*********************************************************
+*                                                      *
+\subsection{The @Doc@ calculus}
+*                                                      *
+*********************************************************
+
+The @Doc@ combinators satisfy the following laws:
+\begin{verbatim}
+Laws for $$
+~~~~~~~~~~~
+<a1>   (x $$ y) $$ z   = x $$ (y $$ z)
+<a2>   empty $$ x      = x
+<a3>   x $$ empty      = x
+
+       ...ditto $+$...
+
+Laws for <>
+~~~~~~~~~~~
+<b1>   (x <> y) <> z   = x <> (y <> z)
+<b2>   empty <> x      = empty
+<b3>   x <> empty      = x
+
+       ...ditto <+>...
+
+Laws for text
+~~~~~~~~~~~~~
+<t1>   text s <> text t        = text (s++t)
+<t2>   text "" <> x            = x, if x non-empty
+
+Laws for nest
+~~~~~~~~~~~~~
+<n1>   nest 0 x                = x
+<n2>   nest k (nest k' x)      = nest (k+k') x
+<n3>   nest k (x <> y)         = nest k z <> nest k y
+<n4>   nest k (x $$ y)         = nest k x $$ nest k y
+<n5>   nest k empty            = empty
+<n6>   x <> nest k y           = x <> y, if x non-empty
+
+** Note the side condition on <n6>!  It is this that
+** makes it OK for empty to be a left unit for <>.
+
+Miscellaneous
+~~~~~~~~~~~~~
+<m1>   (text s <> x) $$ y = text s <> ((text "" <> x)) $$ 
+                                        nest (-length s) y)
+
+<m2>   (x $$ y) <> z = x $$ (y <> z)
+       if y non-empty
+
+
+Laws for list versions
+~~~~~~~~~~~~~~~~~~~~~~
+<l1>   sep (ps++[empty]++qs)   = sep (ps ++ qs)
+       ...ditto hsep, hcat, vcat, fill...
+
+<l2>   nest k (sep ps) = sep (map (nest k) ps)
+       ...ditto hsep, hcat, vcat, fill...
+
+Laws for oneLiner
+~~~~~~~~~~~~~~~~~
+<o1>   oneLiner (nest k p) = nest k (oneLiner p)
+<o2>   oneLiner (x <> y)   = oneLiner x <> oneLiner y 
+\end{verbatim}
+
+
+You might think that the following verion of <m1> would
+be neater:
+\begin{verbatim}
+<3 NO> (text s <> x) $$ y = text s <> ((empty <> x)) $$ 
+                                        nest (-length s) y)
+\end{verbatim}
+But it doesn't work, for if x=empty, we would have
+\begin{verbatim}
+       text s $$ y = text s <> (empty $$ nest (-length s) y)
+                   = text s <> nest (-length s) y
+\end{verbatim}
+
+
+
+*********************************************************
+*                                                      *
+\subsection{Simple derived definitions}
+*                                                      *
+*********************************************************
 
 \begin{code}
-ppIntersperse sep ps = ppBesides (pi ps)
-  where
-   pi []       = []
-   pi [x]      = [x]
-   pi (x:xs)   = (ppBeside x sep) : pi xs
+semi  = char ';'
+colon = char ':'
+comma = char ','
+space = char ' '
+equals = char '='
+lparen = char '('
+rparen = char ')'
+lbrack = char '['
+rbrack = char ']'
+lbrace = char '{'
+rbrace = char '}'
+
+int     n = text (show n)
+integer  n = text (show n)
+float    n = text (show n)
+double   n = text (show n)
+--ORIG: rational n = text (show n)
+rational n = text (show (fromRationalX n)) -- _showRational 30 n)
+
+quotes p       = char '`' <> p <> char '\''
+doubleQuotes p = char '"' <> p <> char '"'
+parens p       = char '(' <> p <> char ')'
+brackets p     = char '[' <> p <> char ']'
+braces p       = char '{' <> p <> char '}'
+
+
+hcat = foldr (<>)  empty
+hsep = foldr (<+>) empty
+vcat = foldr ($$)  empty
+
+hang d1 n d2 = d1 $$ (nest n d2)
+
+punctuate p []     = []
+punctuate p (d:ds) = go d ds
+                  where
+                    go d [] = [d]
+                    go d (e:es) = (d <> p) : go e es
 \end{code}
 
-Laziness is important in @ppBeside@.  If the first thing is not a
-single line it will return @False@ for the single-line boolean without
-laying out the second.
 
+*********************************************************
+*                                                      *
+\subsection{The @Doc@ data type}
+*                                                      *
+*********************************************************
+
+A @Doc@ represents a {\em set} of layouts.  A @Doc@ with
+no occurrences of @Union@ or @NoDoc@ represents just one layout.
 \begin{code}
-ppBeside p1 p2 width is_vert
-  = case (p1 width False) of
-      MkPrettyRep seq1 (MkDelay ll1) emp1 sl1 ->
-         MkPrettyRep (seq1 `cAppend` (cIndent ll1 seq2))
-                     (MkDelay (ll1 + ll2))
-                     (emp1 && emp2)
-                     ((width >= 0) && (sl1 && sl2))
-                     -- This sequence of (&&)'s ensures that ppBeside
-                     -- returns a False for sl as soon as possible.
-       where -- NB: for case alt
-        seq2 = forceInfo x_ll2 emp2 sl2 x_seq2
-        MkDelay ll2 = x_ll2
-        MkPrettyRep x_seq2 x_ll2 emp2 sl2 = p2 (width-ll1) False
-        -- ToDo: if emp{1,2} then we really
-        -- should be passing on "is_vert" to p{2,1}.
-
-ppBesides [] = ppNil
-ppBesides ps = foldr1 ppBeside ps
+data Doc
+ = Empty                               -- empty
+ | NilAbove Doc                                -- text "" $$ x
+ | TextBeside TextDetails INT Doc      -- text s <> x  
+ | Nest INT Doc                                -- nest k x
+ | Union Doc Doc                       -- ul `union` ur
+ | NoDoc                               -- The empty set of documents
+ | Beside Doc Bool Doc                 -- True <=> space between
+ | Above  Doc Bool Doc                 -- True <=> never overlap
+
+type RDoc = Doc                -- RDoc is a "reduced Doc", guaranteed not to have a top-level Above or Beside
+
+
+reduceDoc :: Doc -> RDoc
+reduceDoc (Beside p g q) = beside p g (reduceDoc q)
+reduceDoc (Above  p g q) = above  p g (reduceDoc q)
+reduceDoc p             = p
+
+
+data TextDetails = Chr  Char
+                | Str  String
+                | PStr FAST_STRING
+space_text = Chr ' '
+nl_text    = Chr '\n'
 \end{code}
 
-@ppBesideSP@ puts two things beside each other separated by a space.
+Here are the invariants:
+\begin{itemize}
+\item
+The argument of @NilAbove@ is never @Empty@. Therefore
+a @NilAbove@ occupies at least two lines.
+
+\item
+The arugment of @TextBeside@ is never @Nest@.
+
+\item 
+The layouts of the two arguments of @Union@ both flatten to the same string.
+
+\item 
+The arguments of @Union@ are either @TextBeside@, or @NilAbove@.
+
+\item
+The right argument of a union cannot be equivalent to the empty set (@NoDoc@).
+If the left argument of a union is equivalent to the empty set (@NoDoc@),
+then the @NoDoc@ appears in the first line.
+
+\item 
+An empty document is always represented by @Empty@.
+It can't be hidden inside a @Nest@, or a @Union@ of two @Empty@s.
+
+\item 
+The first line of every layout in the left argument of @Union@
+is longer than the first line of any layout in the right argument.
+(1) ensures that the left argument has a first line.  In view of (3),
+this invariant means that the right argument must have at least two
+lines.
+\end{itemize}
 
 \begin{code}
-ppBesideSP p1 p2 width is_vert
-  = case (p1 width False) of
-      MkPrettyRep seq1 (MkDelay ll1) emp1 sl1 ->
-         MkPrettyRep (seq1 `cAppend` (sp `cAppend` (cIndent li seq2)))
-                  (MkDelay (li + ll2))
-                  (emp1 && emp2)
-                  ((width >= wi) && (sl1 && sl2))
-       where -- NB: for case alt
-        seq2 = forceInfo x_ll2 emp2 sl2 x_seq2
-        MkDelay ll2 = x_ll2
-        MkPrettyRep x_seq2 x_ll2 emp2 sl2 = p2 (width-li) False
-        li, wi :: Int
-        li = if emp1 then 0 else ll1+1
-        wi = if emp1 then 0 else 1
-        sp = if emp1 || emp2 then cNil else (cCh ' ')
+       -- Arg of a NilAbove is always an RDoc
+nilAbove_ p = LOCAL_ASSERT( ok p ) NilAbove p
+           where
+             ok Empty = False
+             ok other = True
+
+       -- Arg of a TextBeside is always an RDoc
+textBeside_ s sl p = TextBeside s sl (LOCAL_ASSERT( ok p ) p)
+                  where
+                    ok (Nest _ _) = False
+                    ok other      = True
+
+       -- Arg of Nest is always an RDoc
+nest_ k p = Nest k (LOCAL_ASSERT( ok p ) p)
+         where
+           ok Empty = False
+           ok other = True
+
+       -- Args of union are always RDocs
+union_ p q = Union (LOCAL_ASSERT( ok p ) p) (LOCAL_ASSERT( ok q ) q)
+          where
+            ok (TextBeside _ _ _) = True
+            ok (NilAbove _)       = True
+            ok (Union _ _)        = True
+            ok other              = False
 \end{code}
 
-@ppCat@ is the name I (WDP) happen to have been using for @ppBesidesSP@.
+
+Notice the difference between
+       * NoDoc (no documents)
+       * Empty (one empty document; no height and no width)
+       * text "" (a document containing the empty string;
+                  one line high, but has no width)
+
+
+
+*********************************************************
+*                                                      *
+\subsection{@empty@, @text@, @nest@, @union@}
+*                                                      *
+*********************************************************
 
 \begin{code}
-ppCat []  = ppNil
-ppCat ps  = foldr1 ppBesideSP ps
+empty = Empty
+
+char  c = textBeside_ (Chr c) 1# Empty
+text  s = case length   s of {IBOX(sl) -> textBeside_ (Str s)  sl Empty}
+ptext s = case _LENGTH_ s of {IBOX(sl) -> textBeside_ (PStr s) sl Empty}
+
+nest IBOX(k)  p = mkNest k (reduceDoc p)       -- Externally callable version
+
+-- mkNest checks for Nest's invariant that it doesn't have an Empty inside it
+mkNest k       (Nest k1 p) = mkNest (k PLUS k1) p
+mkNest k       NoDoc       = NoDoc
+mkNest k       Empty       = Empty
+mkNest ILIT(0) p          = p                  -- Worth a try!
+mkNest k       p          = nest_ k p
+
+-- mkUnion checks for an empty document
+mkUnion Empty q = Empty
+mkUnion p q     = p `union_` q
 \end{code}
 
+*********************************************************
+*                                                      *
+\subsection{Vertical composition @$$@}
+*                                                      *
+*********************************************************
+
+
 \begin{code}
-ppAbove p1 p2 width is_vert
-  = case (p1 width True) of
-      MkPrettyRep seq1 (MkDelay ll1) emp1 sl1 ->
-         MkPrettyRep (seq1 `cAppend` (nl `cAppend` seq2))
-                     (MkDelay ll2)
-                     -- ToDo: make ll depend on empties?
-                     (emp1 && emp2)
-                     False
-       where -- NB: for case alt
-        nl = if emp1 || emp2 then cNil else cNL
-        seq2 = forceInfo x_ll2 emp2 sl2 x_seq2
-        MkDelay ll2 = x_ll2 -- Don't "optimise" this away!
-        MkPrettyRep x_seq2 x_ll2 emp2 sl2 = p2 width True
-            -- ToDo: ditto about passing is_vert if empties
-
-ppAboves [] = ppNil
-ppAboves ps = foldr1 ppAbove ps
+p $$  q = Above p False q
+p $+$ q = Above p True q
+
+above :: Doc -> Bool -> RDoc -> RDoc
+above (Above p g1 q1)  g2 q2 = above p g1 (above q1 g2 q2)
+above p@(Beside _ _ _) g  q  = aboveNest (reduceDoc p) g ILIT(0) (reduceDoc q)
+above p g q                 = aboveNest p             g ILIT(0) (reduceDoc q)
+
+aboveNest :: RDoc -> Bool -> INT -> RDoc -> RDoc
+-- Specfication: aboveNest p g k q = p $g$ (nest k q)
+
+aboveNest NoDoc               g k q = NoDoc
+aboveNest (p1 `Union` p2)     g k q = aboveNest p1 g k q `union_` 
+                                     aboveNest p2 g k q
+                               
+aboveNest Empty               g k q = mkNest k q
+aboveNest (Nest k1 p)         g k q = nest_ k1 (aboveNest p g (k MINUS k1) q)
+                                 -- p can't be Empty, so no need for mkNest
+                               
+aboveNest (NilAbove p)        g k q = nilAbove_ (aboveNest p g k q)
+aboveNest (TextBeside s sl p) g k q = textBeside_ s sl rest
+                                   where
+                                     k1   = k MINUS sl
+                                     rest = case p of
+                                               Empty -> nilAboveNest g k1 q
+                                               other -> aboveNest  p g k1 q
 \end{code}
 
 \begin{code}
-ppNest n p width False = p width False
-ppNest n p width True
-  = case (p (width-n) True) of
-      MkPrettyRep seq (MkDelay ll) emp sl ->
-       MkPrettyRep (cIndent n seq) (MkDelay (ll+n)) emp sl
+nilAboveNest :: Bool -> INT -> RDoc -> RDoc
+-- Specification: text s <> nilaboveNest g k q 
+--             = text s <> (text "" $g$ nest k q)
+
+nilAboveNest g k Empty       = Empty   -- Here's why the "text s <>" is in the spec!
+nilAboveNest g k (Nest k1 q) = nilAboveNest g (k PLUS k1) q
+
+nilAboveNest g k q          | (not g) && (k GR ILIT(0))        -- No newline if no overlap
+                            = textBeside_ (Str (spaces k)) k q
+                            | otherwise                        -- Put them really above
+                            = nilAbove_ (mkNest k q)
 \end{code}
 
-The length-check below \tr{(ll1+ll2+1) <= width} should really check for
-max widths not the width of the last line.
+
+*********************************************************
+*                                                      *
+\subsection{Horizontal composition @<>@}
+*                                                      *
+*********************************************************
 
 \begin{code}
-ppHang p1 n p2 width is_vert   -- This is a little bit stricter than it could
-                               -- be made with a little more effort.
-                               -- Eg the output always starts with seq1
-  = case (p1 width False) of
-      MkPrettyRep seq1 (MkDelay ll1) emp1 sl1 ->
-         if emp1 then
-             p2 width is_vert
-         else
-         if (ll1 <= n) || sl2 then     -- very ppBesideSP'ish
-             -- Hang it if p1 shorter than indent or if it doesn't fit
-             MkPrettyRep (seq1 `cAppend` ((cCh ' ') `cAppend` (cIndent (ll1+1) seq2)))
-                       (MkDelay (ll1 + 1 + ll2))
-                       False
-                       (sl1 && sl2)
-         else
-             -- Nest it (pretty ppAbove-ish)
-             MkPrettyRep (seq1 `cAppend` (cNL `cAppend` (cIndent n seq2')))
-                       (MkDelay ll2') -- ToDo: depend on empties
-                       False
-                       False
-       where -- NB: for case alt
-        seq2 = forceInfo x_ll2 emp2 sl2 x_seq2
-        MkDelay ll2 = x_ll2
-        MkPrettyRep x_seq2 x_ll2 emp2 sl2 = p2 (width-(ll1+1)) False
-            -- ToDo: more "is_vert if empty" stuff
-
-        seq2' = forceInfo x_ll2' emp2' sl2' x_seq2'
-        MkDelay ll2' = x_ll2'          -- Don't "optimise" this away!
-        MkPrettyRep x_seq2' x_ll2' emp2' sl2' = p2 (width-n) False     -- ToDo: True?
+p <>  q = Beside p False q
+p <+> q = Beside p True  q
+
+beside :: Doc -> Bool -> RDoc -> RDoc
+-- Specification: beside g p q = p <g> q
+beside NoDoc               g q   = NoDoc
+beside (p1 `Union` p2)     g q   = (beside p1 g q) `union_` (beside p2 g q)
+beside Empty               g q   = q
+beside (Nest k p)          g q   = nest_ k (beside p g q)      -- p non-empty
+beside p@(Beside p1 g1 q1) g2 q2 
+          {- (A `op1` B) `op2` C == A `op1` (B `op2` C)  iff op1 == op2 
+                                                [ && (op1 == <> || op1 == <+>) ] -}
+         | g1 == g2              = beside p1 g1 (beside q1 g2 q2)
+         | otherwise             = beside (reduceDoc p) g2 q2
+beside p@(Above _ _ _)     g q   = beside (reduceDoc p) g q
+beside (NilAbove p)        g q   = nilAbove_ (beside p g q)
+beside (TextBeside s sl p) g q   = textBeside_ s sl rest
+                              where
+                                 rest = case p of
+                                          Empty -> nilBeside g q
+                                          other -> beside p g q
+\end{code}
+
+\begin{code}
+nilBeside :: Bool -> RDoc -> RDoc
+-- Specification: text "" <> nilBeside g p 
+--             = text "" <g> p
+
+nilBeside g Empty      = Empty -- Hence the text "" in the spec
+nilBeside g (Nest _ p) = nilBeside g p
+nilBeside g p         | g         = textBeside_ space_text ILIT(1) p
+                      | otherwise = p
+\end{code}
+
+*********************************************************
+*                                                      *
+\subsection{Separate, @sep@, Hughes version}
+*                                                      *
+*********************************************************
+
+\begin{code}
+-- Specification: sep ps  = oneLiner (hsep ps)
+--                        `union`
+--                         vcat ps
+
+sep = sepX True                -- Separate with spaces
+cat = sepX False       -- Don't
+
+sepX x []     = empty
+sepX x (p:ps) = sep1 x (reduceDoc p) ILIT(0) ps
+
+
+-- Specification: sep1 g k ys = sep (x : map (nest k) ys)
+--                           = oneLiner (x <g> nest k (hsep ys))
+--                             `union` x $$ nest k (vcat ys)
+
+sep1 :: Bool -> RDoc -> INT -> [Doc] -> RDoc
+sep1 g NoDoc              k ys = NoDoc
+sep1 g (p `Union` q)       k ys = sep1 g p k ys
+                                 `union_`
+                                 (aboveNest q False k (reduceDoc (vcat ys)))
+
+sep1 g Empty               k ys = mkNest k (sepX g ys)
+sep1 g (Nest n p)          k ys = nest_ n (sep1 g p (k MINUS n) ys)
+
+sep1 g (NilAbove p)        k ys = nilAbove_ (aboveNest p False k (reduceDoc (vcat ys)))
+sep1 g (TextBeside s sl p) k ys = textBeside_ s sl (sepNB g p (k MINUS sl) ys)
+
+-- Specification: sepNB p k ys = sep1 (text "" <> p) k ys
+-- Called when we have already found some text in the first item
+-- We have to eat up nests
+
+sepNB g (Nest _ p)  k ys  = sepNB g p k ys
+
+sepNB g Empty k ys        = oneLiner (nilBeside g (reduceDoc rest))
+                               `mkUnion` 
+                           nilAboveNest False k (reduceDoc (vcat ys))
+                         where
+                           rest | g         = hsep ys
+                                | otherwise = hcat ys
+
+sepNB g p k ys           = sep1 g p k ys
 \end{code}
 
+*********************************************************
+*                                                      *
+\subsection{@fill@}
+*                                                      *
+*********************************************************
+
 \begin{code}
-ppSep []  width is_vert = ppNil width is_vert
-ppSep [p] width is_vert = p     width is_vert
-
--- CURRENT, but BAD.  Quadratic behaviour on the perfectly reasonable
---     ppSep [a, ppSep[b, ppSep [c, ... ]]]
-
-ppSep ps  width is_vert
-  = case (ppCat ps width is_vert) of
-      MkPrettyRep seq x_ll emp sl ->
-       if sl then                      -- Fits on one line
-          MkPrettyRep seq x_ll emp sl
-       else
-          ppAboves ps width is_vert    -- Takes several lines
+fsep = fill True
+fcat = fill False
+
+-- Specification: 
+--   fill []  = empty
+--   fill [p] = p
+--   fill (p1:p2:ps) = oneLiner p1 <#> nest (length p1) 
+--                                         (fill (oneLiner p2 : ps))
+--                    `union`
+--                     p1 $$ fill ps
+
+fill g []     = empty
+fill g (p:ps) = fill1 g (reduceDoc p) ILIT(0) ps
+
+
+fill1 :: Bool -> RDoc -> INT -> [Doc] -> Doc
+fill1 g NoDoc              k ys = NoDoc
+fill1 g (p `Union` q)       k ys = fill1 g p k ys
+                                  `union_`
+                                  (aboveNest q False k (fill g ys))
+
+fill1 g Empty               k ys = mkNest k (fill g ys)
+fill1 g (Nest n p)          k ys = nest_ n (fill1 g p (k MINUS n) ys)
+
+fill1 g (NilAbove p)        k ys = nilAbove_ (aboveNest p False k (fill g ys))
+fill1 g (TextBeside s sl p) k ys = textBeside_ s sl (fillNB g p (k MINUS sl) ys)
+
+fillNB g (Nest _ p)  k ys  = fillNB g p k ys
+fillNB g Empty k []        = Empty
+fillNB g Empty k (y:ys)    = nilBeside g (fill1 g (oneLiner (reduceDoc y)) k1 ys)
+                            `mkUnion` 
+                            nilAboveNest False k (fill g (y:ys))
+                          where
+                            k1 | g         = k MINUS ILIT(1)
+                               | otherwise = k
+
+fillNB g p k ys                   = fill1 g p k ys
 \end{code}
 
 
-@speakNth@ converts an integer to a verbal index; eg 1 maps to
-``first'' etc.
+*********************************************************
+*                                                      *
+\subsection{Selecting the best layout}
+*                                                      *
+*********************************************************
 
 \begin{code}
-speakNth :: Int -> Pretty
-
-speakNth 1 = ppPStr SLIT("first")
-speakNth 2 = ppPStr SLIT("second")
-speakNth 3 = ppPStr SLIT("third")
-speakNth 4 = ppPStr SLIT("fourth")
-speakNth 5 = ppPStr SLIT("fifth")
-speakNth 6 = ppPStr SLIT("sixth")
-speakNth n = ppBesides [ ppInt n, ppStr st_nd_rd_th ]
+best :: Mode
+     -> Int            -- Line length
+     -> Int            -- Ribbon length
+     -> RDoc
+     -> RDoc           -- No unions in here!
+
+best OneLineMode IBOX(w) IBOX(r) p
+  = get p
+  where
+    get Empty               = Empty
+    get NoDoc               = NoDoc
+    get (NilAbove p)        = nilAbove_ (get p)
+    get (TextBeside s sl p) = textBeside_ s sl (get p)
+    get (Nest k p)          = get p            -- Elide nest
+    get (p `Union` q)       = first (get p) (get q)
+
+best mode IBOX(w) IBOX(r) p
+  = get w p
   where
-    st_nd_rd_th | n_rem_10 == 1 = "st"
-               | n_rem_10 == 2 = "nd"
-               | n_rem_10 == 3 = "rd"
-               | otherwise     = "th"
+    get :: INT         -- (Remaining) width of line
+        -> Doc -> Doc
+    get w Empty               = Empty
+    get w NoDoc               = NoDoc
+    get w (NilAbove p)        = nilAbove_ (get w p)
+    get w (TextBeside s sl p) = textBeside_ s sl (get1 w sl p)
+    get w (Nest k p)          = nest_ k (get (w MINUS k) p)
+    get w (p `Union` q)       = nicest w r (get w p) (get w q)
+
+    get1 :: INT                -- (Remaining) width of line
+         -> INT                -- Amount of first line already eaten up
+         -> Doc                -- This is an argument to TextBeside => eat Nests
+         -> Doc                -- No unions in here!
+
+    get1 w sl Empty               = Empty
+    get1 w sl NoDoc               = NoDoc
+    get1 w sl (NilAbove p)        = nilAbove_ (get (w MINUS sl) p)
+    get1 w sl (TextBeside t tl p) = textBeside_ t tl (get1 w (sl PLUS tl) p)
+    get1 w sl (Nest k p)          = get1 w sl p
+    get1 w sl (p `Union` q)       = nicest1 w r sl (get1 w sl p) 
+                                                  (get1 w sl q)
+
+nicest w r p q = nicest1 w r ILIT(0) p q
+nicest1 w r sl p q | fits ((w `minn` r) MINUS sl) p = p
+                  | otherwise                   = q
+
+fits :: INT    -- Space available
+     -> Doc
+     -> Bool   -- True if *first line* of Doc fits in space available
+fits n p    | n LT ILIT(0) = False
+fits n NoDoc               = False
+fits n Empty               = True
+fits n (NilAbove _)        = True
+fits n (TextBeside _ sl p) = fits (n MINUS sl) p
+
+minn x y | x LT y    = x
+        | otherwise = y
+\end{code}
+
+@first@ and @nonEmptySet@ are similar to @nicest@ and @fits@, only simpler.
+@first@ returns its first argument if it is non-empty, otherwise its second.
 
-    n_rem_10 = n `rem` 10
+\begin{code}
+first p q | nonEmptySet p = p 
+         | otherwise     = q
+
+nonEmptySet NoDoc           = False
+nonEmptySet (p `Union` q)      = True
+nonEmptySet Empty             = True
+nonEmptySet (NilAbove p)       = True          -- NoDoc always in first line
+nonEmptySet (TextBeside _ _ p) = nonEmptySet p
+nonEmptySet (Nest _ p)         = nonEmptySet p
 \end{code}
 
+@oneLiner@ returns the one-line members of the given set of @Doc@s.
+
+\begin{code}
+oneLiner :: Doc -> Doc
+oneLiner NoDoc               = NoDoc
+oneLiner Empty               = Empty
+oneLiner (NilAbove p)        = NoDoc
+oneLiner (TextBeside s sl p) = textBeside_ s sl (oneLiner p)
+oneLiner (Nest k p)          = nest_ k (oneLiner p)
+oneLiner (p `Union` q)       = oneLiner p
+\end{code}
+
+
+
+*********************************************************
+*                                                      *
+\subsection{Displaying the best layout}
+*                                                      *
+*********************************************************
 
-%************************************************************************
-%*                                                                     *
-\subsection[Outputable-print]{Pretty-printing stuff}
-%*                                                                     *
-%************************************************************************
 
 \begin{code}
-#if defined(COMPILING_GHC)
-    -- to the end of file
+{-
+renderStyle Style{mode, lineLength, ribbonsPerLine} doc 
+  = fullRender mode lineLength ribbonsPerLine doc ""
+-}
 
-prettyToUn :: Pretty -> Unpretty
+render doc       = showDoc doc ""
+showDoc doc rest = fullRender PageMode 100 1.5 string_txt rest doc
 
-prettyToUn p
-  = case (p 999999{-totally bogus width-} False{-also invented-}) of
-      MkPrettyRep seq ll emp sl -> seq
+string_txt (Chr c)   s  = c:s
+string_txt (Str s1)  s2 = s1 ++ s2
+string_txt (PStr s1) s2 = _UNPK_ s1 ++ s2
+\end{code}
 
-#endif {-COMPILING_GHC-}
+\begin{code}
+
+fullRender OneLineMode _ _ txt end doc = easy_display space_text txt end (reduceDoc doc)
+fullRender LeftMode    _ _ txt end doc = easy_display nl_text    txt end (reduceDoc doc)
+
+fullRender mode line_length ribbons_per_line txt end doc
+  = display mode line_length ribbon_length txt end best_doc
+  where 
+    best_doc = best mode hacked_line_length ribbon_length (reduceDoc doc)
+
+    hacked_line_length, ribbon_length :: Int
+    ribbon_length = round (fromInt line_length / ribbons_per_line)
+    hacked_line_length = case mode of { ZigZagMode -> MAXINT; other -> line_length }
+
+display mode IBOX(page_width) IBOX(ribbon_width) txt end doc
+  = case page_width MINUS ribbon_width of { gap_width ->
+    case gap_width DIV ILIT(2) of { shift ->
+    let
+       lay k (Nest k1 p)  = lay (k PLUS k1) p
+       lay k Empty        = end
+    
+       lay k (NilAbove p) = nl_text `txt` lay k p
+    
+       lay k (TextBeside s sl p)
+           = case mode of
+                   ZigZagMode |  k GREQ gap_width
+                              -> nl_text `txt` (
+                                 Str (multi_ch shift '/') `txt` (
+                                 nl_text `txt` (
+                                 lay1 (k MINUS shift) s sl p)))
+
+                              |  k LT ILIT(0)
+                              -> nl_text `txt` (
+                                 Str (multi_ch shift '\\') `txt` (
+                                 nl_text `txt` (
+                                 lay1 (k PLUS shift) s sl p )))
+
+                   other -> lay1 k s sl p
+    
+       lay1 k s sl p = Str (indent k) `txt` (s `txt` lay2 (k PLUS sl) p)
+    
+       lay2 k (NilAbove p)        = nl_text `txt` lay k p
+       lay2 k (TextBeside s sl p) = s `txt` (lay2 (k PLUS sl) p)
+       lay2 k (Nest _ p)          = lay2 k p
+       lay2 k Empty               = end
+    in
+    lay ILIT(0) doc
+    }}
+
+cant_fail = error "easy_display: NoDoc"
+easy_display nl_text txt end doc 
+  = lay doc cant_fail
+  where
+    lay NoDoc              no_doc = no_doc
+    lay (Union p q)        no_doc = {- lay p -} (lay q cant_fail)              -- Second arg can't be NoDoc
+    lay (Nest k p)         no_doc = lay p no_doc
+    lay Empty              no_doc = end
+    lay (NilAbove p)       no_doc = nl_text `txt` lay p cant_fail      -- NoDoc always on first line
+    lay (TextBeside s sl p) no_doc = s `txt` lay p no_doc
+
+indent n | n GREQ ILIT(8) = '\t' : indent (n MINUS ILIT(8))
+        | otherwise      = spaces n
+
+multi_ch ILIT(0) ch = ""
+multi_ch n       ch = ch : multi_ch (n MINUS ILIT(1)) ch
+
+spaces ILIT(0) = ""
+spaces n       = ' ' : spaces (n MINUS ILIT(1))
 \end{code}
 
+Doesn't really belong here..
+
 -----------------------------------
 \begin{code}
 -- from Lennart