> module Print where
> import LambdaLift
> import Utilities
This section gives a small pretty-printer for the language.
> pprint :: (binder -> Iseq) -> Expr binder -> [Char]
> pprint pb e = i_mkstr (ppr pb e)
pprintExpr prints expressions whose binder is just a name
> pprintExpr = pprint i_str
pprintLevel prints expressions whose binder is an (Expr, Level) pair.
> pprintLevel :: (Expr ([Char], Integer)) -> [Char]
>
> pprintLevel = pprint (\(name,level) -> i_concat [ i_str name, i_str "{",
> i_num level, i_str "}" ])
Now we have to print supercombinators too:
> pprintSCs scs = i_mkstr (i_concat (map ppsc scs))
> ppsc (name, args, rhs) =
> i_concat [ i_newline, i_str name, i_str " ",
> i_interleave (i_str " ") (map i_str args),
> i_str " = ",
> i_indent 6 (ppr i_str rhs) ]
The function @ppr@ does most of the work.
> ppr pb (EAp e1 e2) = i_concat [ppr pb e1, i_space, ppr_atomic pb e2]
> ppr pb (ELet isrec defs e) =
> i_concat [ i_str keyword, i_newline,
> i_indent 4 (i_interleave (i_str ";\n") (map (ppr_def pb) defs)),
> i_str "\nin ",
> ppr pb e
> ]
> where
> keyword | isrec = "letrec"
> | not isrec = "let"
> ppr pb (ELam args body) =
> i_concat [ i_str "\\[",
> i_interleave (i_str ",") (map pb args),
> i_str "] ", ppr pb body ]
>
> ppr pb e = ppr_atomic pb e
>
> ppr_atomic pb (EConst (CNum n)) = i_num n
> ppr_atomic pb (EConst (CFun name)) = i_str name
> ppr_atomic pb (EConst (CBool b)) = i_str (show b)
> ppr_atomic pb (EVar v) = i_str v
> ppr_atomic pb e = i_concat [i_str "(", ppr pb e, i_str ")"]
@ppr_def@ constructs the @iseq@ for a definition. The only
complication is that the arguments may be annotated.
> ppr_def pb (binder, (ELam args body)) =
> i_concat [ pb binder, i_space,
> i_interleave i_space (map pb args),
> i_str " = ",
> ppr pb body
> ]
> ppr_def pb (binder, rhs) = i_concat [pb binder, i_str " = ", ppr pb rhs]
>
%> example =
%> ELet Recursive
%> [("f", ["x","y"], EAnnot (AnnotArgs [Strict, NonStrict]) (EVar "y")),
%> ("g", ["x","y"], EAnnot (AnnotArgs [Strict, Strict]) (EVar "y"))]
%> (EVar "f")
\section{A pretty-printing data type}
\label{pretty-type}
\section{Specification}
The type @iseq@ can be thought of as much like a list of characters;
the empty @iseq@ is @i_nil@, and @iseq@s can be concatenated with
@i_append@. A list of @iseq@s can be concatenated into a single
@iseq@ using @i_concat@.
You can convert a string to an @iseq@ with @i_str@, a number into an @iseq@
with @i_num@, and an @iseq@ to
a string with @i_mkstr@.
What makes @iseq@s different from strings is one operation, @i_indent@,
and a performance property:
\begin{itemize}
\item
The performance property is that, unlike @++@, @i_append@ works in
constant time.
\item
The @i_indent@ operation takes a number $n$ and an @iseq@ and returns an
@iseq@ which you can think of as a string in which every newline
has been replaced with a newline followed by $n$ spaces.
Like @i_append@ though, @i_indent@ takes constant time.
\end{itemize}
> type Iseq = Oseq -> Oseq
The following functions can be implemented in terms of the fundamental ones
above.
@i_concat@ concatenates together the members of a list of @Iseq@s.
> i_concat :: [Iseq] -> Iseq
> i_concat = foldr i_append i_nil
@i_interleave@ is like @i_concat@, except that it interleaves a given
@iseq@ between successive elements:
> i_interleave :: Iseq -> [Iseq] -> Iseq
> i_interleave is [] = i_nil
> i_interleave is iss = foldr1 glue iss
> where glue is1 is2 = is1 `i_append` (is `i_append` is2)
> foldr1 f [x] = x
> foldr1 f (x:xs) = f x (foldr1 f xs)
Finally @i_num@ converts an integer to an @Iseq@.
> i_num :: Num a => a -> Iseq
> i_num = i_str . show
> i_newline = i_str "\n"
> i_space = i_str " "
An @Oseq@ is a function whose arguments are a number giving the
desired indentation and a boolean saying whether this indentation is
required right at the beginning of the result, and delivers as
result a character string indented by the appropriate amount.
> type Oseq = Int -> Bool -> [Char]
Here are some useful defintions using @oseq@:
> o_empty :: Oseq -- An empty oseq
> o_empty indent npend = []
>
> o_mkstr :: Oseq -> [Char]
> o_mkstr oseq = oseq 0 False
An @Iseq@ is just like an @Oseq@, except that it takes an @oseq@ as an
argument, which it appends to its result.
This definition of @Oseq@ leads directly to the following definitions:
> i_nil x = x
> i_append = (.)
> i_str = foldr (i_append . i_char) i_nil
> i_mkstr iseq = o_mkstr (iseq o_empty)
The definition of @i_char@, which turns a character into an @Iseq@
contains most of the subtlety. If the character is a newline, return
a newline, but set the pending-indent flag to @True@;
if it isn't a newline, and the pending-indent flag is set, then
spit out the spaces for the indent and reset the flag; otherwise just
return the character:
> i_char :: Char -> Iseq
> i_char '\n' rest indent npend = '\n' : rest indent True
> i_char c rest indent False = c : rest indent False
> i_char c rest indent True = pspaces indent (c : rest indent False)
The @i_indent@ function manipulates the @indent@ arguments of its
component @Oseq@s:
> i_indent n iseq oseq indent npend =
> iseq oseq' (indent+n) npend
> where
> oseq' indent' npend' = oseq indent npend'
> -- Ignore the indent passed along to oseq;
> -- use the original indent instead.
@spaces@ is just an auxilliary function:
> pspaces 0 cs = cs
> pspaces n cs = ' ' : pspaces (n-1) cs
|