144 lines
3.9 KiB
Idris
144 lines
3.9 KiB
Idris
||| A prettier printer, Philip Wadler
|
|
||| https://homepages.inf.ed.ac.uk/wadler/papers/prettier/prettier.pdf
|
|
module Lib.Prettier
|
|
|
|
import Data.String
|
|
import Data.Nat
|
|
import Data.Maybe
|
|
|
|
||| `Doc` is a pretty printing document. Constructors are private, use
|
|
||| methods below. `Alt` in particular has some invariants on it, see paper
|
|
||| for details. (Something along the lines of "the first line of left is not
|
|
||| bigger than the first line of the right".)
|
|
export
|
|
data Doc = Empty | Line | Text String | Nest Nat Doc | Seq Doc Doc | Alt Doc Doc
|
|
|
|
||| The original paper had a List-like structure Doc (the above was DOC) which
|
|
||| had Empty and a tail on Text and Line.
|
|
data Item = TEXT String | LINE Nat
|
|
|
|
export
|
|
empty : Doc
|
|
empty = Empty
|
|
|
|
flatten : Doc -> Doc
|
|
flatten Empty = Empty
|
|
flatten (Seq x y) = Seq (flatten x) (flatten y)
|
|
flatten (Nest i x) = flatten x
|
|
flatten Line = Text " "
|
|
flatten (Text str) = Text str
|
|
flatten (Alt x y) = flatten x
|
|
|
|
group : Doc -> Doc
|
|
group x = Alt (flatten x) x
|
|
|
|
-- TODO - we can accumulate snoc and cat all at once
|
|
layout : List Item -> SnocList String -> String
|
|
layout [] acc = fastConcat $ cast acc
|
|
layout (LINE k :: x) acc = layout x (acc :< "\n" :< replicate k ' ')
|
|
layout (TEXT str :: x) acc = layout x (acc :< str)
|
|
|
|
||| Whether a documents first line fits.
|
|
fits : Nat -> List Item -> Bool
|
|
fits 0 x = False
|
|
fits w ((TEXT s) :: xs) = fits (w `minus` length s) xs
|
|
fits w _ = True
|
|
|
|
-- vs Wadler, we're collecting the left side as a SnocList to prevent
|
|
-- blowing out the stack on the Text case. The original had DOC as
|
|
-- a Linked-List like structure (now List Item)
|
|
|
|
-- I've now added a `fit` boolean to indicate if we should cut when we hit the line length
|
|
-- Wadler was relying on laziness to stop the first branch before LINE if necessary
|
|
be : Bool -> SnocList Item -> Nat -> Nat -> List (Nat, Doc) -> Maybe (List Item)
|
|
be fit acc w k [] = Just (acc <>> [])
|
|
be fit acc w k ((i, Empty) :: xs) = be fit acc w k xs
|
|
be fit acc w k ((i, Line) :: xs) = (be False (acc :< LINE i) w i xs)
|
|
be fit acc w k ((i, (Text s)) :: xs) =
|
|
if not fit || (k + length s < w)
|
|
then (be fit (acc :< TEXT s) w (k + length s) xs)
|
|
else Nothing
|
|
be fit acc w k ((i, (Nest j x)) :: xs) = be fit acc w k ((i+j,x)::xs)
|
|
be fit acc w k ((i, (Seq x y)) :: xs) = be fit acc w k ((i,x) :: (i,y) :: xs)
|
|
be fit acc w k ((i, (Alt x y)) :: xs) =
|
|
(acc <>>) <$> (be True [<] w k ((i,x)::xs) <|> be fit [<] w k ((i,y)::xs))
|
|
|
|
best : Nat -> Nat -> Doc -> List Item
|
|
best w k x = fromMaybe [] $ be False [<] w k [(0,x)]
|
|
|
|
-- Public interface
|
|
public export
|
|
interface Pretty a where
|
|
pretty : a -> Doc
|
|
|
|
export
|
|
render : Nat -> Doc -> String
|
|
render w x = layout (best w 0 x) [<]
|
|
|
|
public export
|
|
Semigroup Doc where x <+> y = Seq x (Seq (Text " ") y)
|
|
|
|
-- Match System.File so we don't get warnings
|
|
public export
|
|
infixl 5 </>
|
|
|
|
export
|
|
line : Doc
|
|
line = Line
|
|
|
|
export
|
|
text : String -> Doc
|
|
text = Text
|
|
|
|
export
|
|
nest : Nat -> Doc -> Doc
|
|
nest = Nest
|
|
|
|
export
|
|
(++) : Doc -> Doc -> Doc
|
|
x ++ y = Seq x y
|
|
|
|
export
|
|
(</>) : Doc -> Doc -> Doc
|
|
x </> y = x ++ line ++ y
|
|
|
|
||| fold, but doesn't emit extra nil
|
|
export
|
|
folddoc : (Doc -> Doc -> Doc) -> List Doc -> Doc
|
|
folddoc f [] = Empty
|
|
folddoc f [x] = x
|
|
folddoc f (x :: xs) = f x (folddoc f xs)
|
|
|
|
||| separate with space
|
|
export
|
|
spread : List Doc -> Doc
|
|
spread = folddoc (<+>)
|
|
|
|
||| separate with new lines
|
|
export
|
|
stack : List Doc -> Doc
|
|
stack = folddoc (</>)
|
|
|
|
||| bracket x with l and r, indenting contents on new line
|
|
export
|
|
bracket : String -> Doc -> String -> Doc
|
|
bracket l x r = group (text l ++ nest 2 (line ++ x) ++ line ++ text r)
|
|
|
|
export infixl 5 <+/>
|
|
|
|
||| Either space or newline
|
|
export
|
|
(<+/>) : Doc -> Doc -> Doc
|
|
x <+/> y = x ++ (text " " `Alt` line) ++ y
|
|
|
|
||| Reformat some docs to fill. Not sure if I want this precise behavior or not.
|
|
export
|
|
fill : List Doc -> Doc
|
|
fill [] = Empty
|
|
fill [x] = x
|
|
fill (x :: y :: xs) = (flatten x <+> fill (flatten y :: xs)) `Alt` (x </> fill (y :: xs))
|
|
|
|
public export
|
|
FromString Doc where
|
|
fromString = text
|