> 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