on master help sign in
A revised Idris port of the Wadler-Leijen pretty-printer.
af3a163Jan de Muijnck-Hughes
CHANGELOG.md+3
@@ -2,6 +2,9 @@
2
2
3## Library Updates
3## Library Updates
4
4
5++ Added `Text.PrettyPrint.WL` an implementation of the Wadler-Leijen
6+ Pretty-Print algorithm. Useful for those wishing to pretty print
7+ things.
5+ Added `Text.Lexer` and `Text.Parser` to `contrib`. These are small libraries
8+ Added `Text.Lexer` and `Text.Parser` to `contrib`. These are small libraries
6 for implementing total lexical analysers and parsers.
9 for implementing total lexical analysers and parsers.
7+ New instances:
10+ New instances:
libs/contrib/Text/PrettyPrint/WL.idrnew file+18
@@ -0,0 +1,18 @@
1+||| A revised Idris port of the Wadler-Leijen pretty-printer.
2+|||
3+||| This port of the Wadler-Leijen Pretty-Printer is based upon the
4+||| port originally created by Shayan Najd [1]. We have changed the
5+||| render function to be total using a technique based on
6+||| continuations proposed by gallais [2], and fix minor errors in the
7+||| original Idris port.
8+|||
9+||| [1] https://github.com/shayan-najd/wl-pprint
10+||| [2] https://gallais.github.io/blog/termination-tricks.html
11+module Text.PrettyPrint.WL
12+
13+import public Text.PrettyPrint.WL.Core
14+import public Text.PrettyPrint.WL.Characters
15+import public Text.PrettyPrint.WL.Combinators
16+
17+%default total
18+%access export
libs/contrib/Text/PrettyPrint/WL/Characters.idrnew file+103
@@ -0,0 +1,103 @@
1+||| Commonly used characters
2+module Text.PrettyPrint.WL.Characters
3+
4+import Text.PrettyPrint.WL.Core
5+
6+%default total
7+%access export
8+
9+lparen : Doc
10+lparen = char '('
11+
12+rparen : Doc
13+rparen = char ')'
14+
15+langle : Doc
16+langle = char '<'
17+
18+rangle : Doc
19+rangle = char '>'
20+
21+lbrace : Doc
22+lbrace = char '{'
23+
24+rbrace : Doc
25+rbrace = char '}'
26+
27+lbracket : Doc
28+lbracket = char '['
29+
30+rbracket : Doc
31+rbracket = char ']'
32+
33+squote : Doc
34+squote = char '\''
35+
36+dquote : Doc
37+dquote = char '"'
38+
39+semi : Doc
40+semi = char ';'
41+
42+colon : Doc
43+colon = char ':'
44+
45+comma : Doc
46+comma = char ','
47+
48+space : Doc
49+space = char ' '
50+
51+dot : Doc
52+dot = char '.'
53+
54+backslash : Doc
55+backslash = char '\\'
56+
57+forwardslash : Doc
58+forwardslash = char '/'
59+
60+equals : Doc
61+equals = char '='
62+
63+pipe : Doc
64+pipe = char '|'
65+
66+bang : Doc
67+bang = char '!'
68+
69+tick : Doc
70+tick = char '`'
71+
72+tilda : Doc
73+tilda = char '~'
74+
75+hash : Doc
76+hash = char '#'
77+
78+asterix : Doc
79+asterix = char '*'
80+
81+ampersand : Doc
82+ampersand = char '&'
83+
84+dash : Doc
85+dash = char '-'
86+
87+question : Doc
88+question = char '?'
89+
90+dollar : Doc
91+dollar = char '$'
92+
93+pound : Doc
94+pound = char '£'
95+
96+euro : Doc
97+euro = char '€'
98+
99+caret : Doc
100+caret = char '^'
101+
102+percent : Doc
103+percent = char '%'
libs/contrib/Text/PrettyPrint/WL/Combinators.idrnew file+259
@@ -0,0 +1,259 @@
1+||| Common combinators.
2+module Text.PrettyPrint.WL.Combinators
3+
4+import Text.PrettyPrint.WL.Core
5+import Text.PrettyPrint.WL.Characters
6+
7+%default total
8+%access export
9+
10+-- --------------------------------------------------- [ Alignment Combinators ]
11+
12+||| The document `(align x)` renders document `x` with the nesting
13+||| level set to the current column. It is used for example to
14+||| implement 'hang'.
15+align : Doc -> Doc
16+align d = column (\k => nesting (\i => nest (k - i) d))
17+
18+||| The hang combinator implements hanging indentation. The document
19+||| `(hang i x)` renders document `x` with a nesting level set to the
20+||| current column plus `i`.
21+hang : Int -> Doc -> Doc
22+hang i d = align (nest i d)
23+
24+||| The document `(indent i x)` indents document `x` with `i` spaces.
25+indent : Int -> Doc -> Doc
26+indent i d = hang i (text (spaces i) <+> d)
27+
28+-- -------------------------------------------------- [ High-Level Combinators ]
29+
30+||| The document `softLine` behaves like `space` if the resulting
31+||| output fits the page, otherwise it behaves like `line`.
32+softLine : Doc
33+softLine = group line
34+
35+||| The document `softbreak` behaves like `empty` if the resulting
36+||| output fits the page, otherwise it behaves like 'line'.
37+softBreak : Doc
38+softBreak = group breakLine
39+
40+infixr 5 |/|, |//|, |$|, |$$|
41+infixr 6 |+|, |++|
42+
43+private
44+concatDoc : Doc -> Doc -> Doc -> Doc
45+concatDoc sep x y = x `beside` (sep `beside` y)
46+
47+||| The document `(x |+| y)` concatenates document `x` and document
48+||| `y`. It is an associative operation having 'empty' as a left and
49+||| right unit.
50+(|+|) : Doc -> Doc -> Doc
51+(|+|) = (<+>)
52+
53+||| The document `(x |++| y)` concatenates document `x` and `y` with a
54+||| `space` in between.
55+(|++|) : Doc -> Doc -> Doc
56+(|++|) = concatDoc space
57+
58+||| The document `(x |/| y)` concatenates document `x` and `y` with a
59+||| 'softline' in between. This effectively puts `x` and `y` either
60+||| next to each other (with a `space` in between) or underneath each
61+||| other.
62+(|/|) : Doc -> Doc -> Doc
63+(|/|) = concatDoc softLine
64+
65+||| The document `(x |//| y)` concatenates document `x` and `y` with
66+||| a 'softbreak' in between. This effectively puts `x` and `y` either
67+||| right next to each other or underneath each other.
68+(|//|) : Doc -> Doc -> Doc
69+(|//|) = concatDoc softBreak
70+
71+||| The document `(x |$| y)` concatenates document `x` and `y` with a
72+||| `line` in between.
73+(|$|) : Doc -> Doc -> Doc
74+(|$|) = concatDoc line
75+
76+||| The document `(x |$$| y)` concatenates document `x` and `y` with
77+||| a `linebreak` in between.
78+(|$$|) : Doc -> Doc -> Doc
79+(|$$|) = concatDoc breakLine
80+
81+||| The document `(vsep xs)` concatenates all documents `xs`
82+||| vertically with `(|$|)`. If a 'group' undoes the line breaks
83+||| inserted by `vsep`, all documents are separated with a space.
84+vsep : List Doc -> Doc
85+vsep = fold (|$|)
86+
87+||| The document `(sep xs)` concatenates all documents `xs` either
88+||| horizontally with `(|++|)`, if it fits the page, or vertically with
89+||| `(|$|)`.
90+sep : List Doc -> Doc
91+sep = group . vsep
92+
93+
94+||| The document `(fillSep xs)` concatenates documents `xs`
95+||| horizontally with `(|++|)` as long as its fits the page, than
96+||| inserts a `line` and continues doing that for all documents in
97+||| `xs`.
98+fillSep : List Doc -> Doc
99+fillSep = fold (|/|)
100+
101+||| The document `(hsep xs)` concatenates all documents `xs`
102+||| horizontally with `(|++|)`.
103+hsep : List Doc -> Doc
104+hsep = fold (|++|)
105+
106+||| The document `(hcat xs)` concatenates all documents `xs`
107+||| horizontally with `(|+|)`.
108+hcat : List Doc -> Doc
109+hcat = fold (<+>)
110+
111+||| The document `(vcat xs)` concatenates all documents `xs`
112+||| vertically with `(|$$|)`. If a `group` undoes the line breaks
113+||| inserted by `vcat`, all documents are directly concatenated.
114+vcat : List Doc -> Doc
115+vcat = fold (|$$|)
116+
117+||| The document `(cat xs)` concatenates all documents `xs` either
118+||| horizontally with `(|+|)`, if it fits the page, or vertically with
119+||| `(|$$|)`.
120+cat : List Doc -> Doc
121+cat = group . vcat
122+
123+||| The document `(fillCat xs)` concatenates documents `xs`
124+||| horizontally with `(|+|)` as long as its fits the page, than inserts
125+||| a `linebreak` and continues doing that for all documents in `xs`.
126+fillCat : List Doc -> Doc
127+fillCat = fold (|//|)
128+
129+||| The document `(enclose l r x)` encloses document `x` between
130+||| documents `l` and `r` using `(|+|)`.
131+enclose : Doc -> Doc -> Doc -> Doc
132+enclose l r x = concatDoc x l r
133+
134+squotes : Doc -> Doc
135+squotes = enclose squote squote
136+
137+dquotes : Doc -> Doc
138+dquotes = enclose dquote dquote
139+
140+braces : Doc -> Doc
141+braces = enclose lbrace rbrace
142+
143+parens : Doc -> Doc
144+parens = enclose lparen rparen
145+
146+angles : Doc -> Doc
147+angles = enclose langle rangle
148+
149+brackets : Doc -> Doc
150+brackets = enclose lbracket rbracket
151+
152+-- ----------------------------------------------------- [ Lists, Sets, Tuples ]
153+
154+||| The document `(encloseSep l r sep xs)` concatenates the documents
155+||| `xs` separated by `sep` and encloses the resulting document by `l`
156+||| and `r`. The documents are rendered horizontally if that fits the
157+||| page. Otherwise they are aligned vertically. All separators are put
158+||| in front of the elements.
159+encloseSep : (l : Doc)
160+ -> (r : Doc)
161+ -> (sep : Doc)
162+ -> (xs : List Doc)
163+ -> Doc
164+encloseSep l r s [] = l <+> r
165+encloseSep l r s [x] = l <+> x <+> r
166+encloseSep l r s xs = align (cat (zipWith (<+>) (l :: replicate (length xs) s) xs) <+> r)
167+
168+||| The document `(list xs)` comma separates the documents `xs` and
169+||| encloses them in square brackets. The documents are rendered
170+||| horizontally if that fits the page. Otherwise they are aligned
171+||| vertically. All comma separators are put in front of the
172+||| elements.
173+list : List Doc -> Doc
174+list = encloseSep lbracket rbracket comma
175+
176+||| The document `(tupled xs)` comma separates the documents `xs`
177+||| and encloses them in parenthesis. The documents are rendered
178+||| horizontally if that fits the page. Otherwise they are aligned
179+||| vertically. All comma separators are put in front of the
180+||| elements.
181+tupled : List Doc -> Doc
182+tupled = encloseSep lparen rparen comma
183+
184+||| The document `(set xs)` separates the documents `xs` with
185+||| commas and encloses them in braces. The documents are
186+||| rendered horizontally if that fits the page. Otherwise they are
187+||| aligned vertically. All comma's are put in front of the
188+||| elements.
189+set : List Doc -> Doc
190+set = encloseSep lbrace rbrace comma
191+
192+||| The document `(semiBraces xs)` separates the documents `xs` with
193+||| semi-colon and encloses them in braces. The documents are
194+||| rendered horizontally if that fits the page. Otherwise they are
195+||| aligned vertically. All semi-colons's are put in front of the
196+||| elements.
197+semiBrace : List Doc -> Doc
198+semiBrace = encloseSep lbrace rbrace semi
199+
200+||| `(punctuate p xs)` concatenates all documents in `xs` with
201+||| document `p` except for the last document.
202+punctuate : Doc -> List Doc -> List Doc
203+punctuate p [] = []
204+punctuate p [x] = [x]
205+punctuate p (x :: xs) = (x <+> p) :: punctuate p xs
206+
207+-- ------------------------------------------------------------------- [ Fills ]
208+
209+width : Doc -> (Int -> Doc) -> Doc
210+width doc f = column (\k => doc <+> column (\k' => f (k' - k)))
211+
212+||| The document `(fillBreak i x)` first renders document `x`. It
213+||| than appends `space`s until the width is equal to `i`. If the
214+||| width of `x` is already larger than `i`, the nesting level is
215+||| increased by `i` and a `line` is appended.
216+fillBreak : (i : Int) -> (x : Doc) -> Doc
217+fillBreak f x =
218+ width x (\w => if (w > f)
219+ then nest f breakLine
220+ else text (spaces (f - w)))
221+
222+||| The document `(fill i x)` renders document `x`. It than appends
223+||| `space`s until the width is equal to `i`. If the width of `x` is
224+||| already larger, nothing is appended. This combinator is quite
225+||| useful in practice to output a list of bindings.
226+fill : (i : Int) -> (x : Doc) -> Doc
227+fill f d =
228+ width d (\w => if (w >= f)
229+ then empty
230+ else text (spaces (f - w)))
231+
232+-- -------------------------------------------------------------- [ Primatives ]
233+
234+||| The document `(literal s)` concatenates all characters in `s`
235+||| using `line` for newline characters and `char` for all other
236+||| characters. It is used instead of 'text' whenever the text contains
237+||| newline characters.
238+literal : (str : String) -> Doc
239+literal str = mkLiteral (unpack str)
240+ where
241+ mkLiteral : List Char -> Doc
242+ mkLiteral Nil = empty
243+ mkLiteral ('\n':: ss) = line <+> mkLiteral ss
244+ mkLiteral (c :: ss) = char c <+> mkLiteral ss
245+
246+bool : Bool -> Doc
247+bool b = text (show b)
248+
249+nat : Nat -> Doc
250+nat n = text (show n)
251+
252+int : Int -> Doc
253+int i = text (show i)
254+
255+integer : Integer -> Doc
256+integer i = text (show i)
257+
258+double : Double -> Doc
259+double d = text (show d)
libs/contrib/Text/PrettyPrint/WL/Core.idrnew file+225
@@ -0,0 +1,225 @@
1+||| Core definitions and primitives.
2+module Text.PrettyPrint.WL.Core
3+
4+%default total
5+%access export
6+
7+-- -------------------------------------------------------------------- [ Core ]
8+
9+||| The abstract data type `Doc` represents pretty documents.
10+|||
11+data Doc : Type where
12+ Empty : Doc
13+ Chara : (c : Char) -> Doc
14+ Text : (len : Int) -> (str : String) -> Doc
15+ Line : (undone : Bool) -> Doc
16+
17+ Cat : (x : Doc) -> (y : Doc) -> Doc
18+ Nest : (lvl : Int) -> (x : Doc) -> Doc
19+ Union : (x : Doc) -> (y : Doc) -> Doc
20+
21+ Column : (f : Int -> Doc) -> Doc
22+ Nesting : (f : Int -> Doc) -> Doc
23+
24+-- -------------------------------------------------------------- [ Primitives ]
25+
26+||| The empty document is, indeed, empty.
27+empty : Doc
28+empty = Empty
29+
30+||| The `line` document advances to the next line and indents to the
31+||| current nesting level. Document `line` behaves like `(text " ")`
32+||| if the line break is undone by 'group'.
33+line : Doc
34+line = Line False
35+
36+||| The document `(char c)` contains the literal character `c`. If the
37+||| character is a newline (`'\n'`) the function returns a line
38+||| document. Ideally, the function 'line' should be used for line
39+||| breaks.
40+char : (c : Char) -> Doc
41+char '\n' = line
42+char c = Chara c
43+
44+||| The document `(text s)` contains the literal string `s`. The
45+||| string shouldn't contain any newline (`'\n'`) characters. If the
46+||| string contains newline characters, the function 'literal' should be
47+||| used.
48+text : (str : String) -> Doc
49+text "" = empty
50+text str = Text (cast $ length str) str
51+
52+||| The `breakLine` document advances to the next line and indents to
53+||| the current nesting level. Document `breakLine` behaves like
54+||| 'empty' if the line break is undone by 'group'.
55+breakLine : Doc
56+breakLine = Line True
57+
58+||| Horizontal concatenation of documents
59+beside : Doc -> Doc -> Doc
60+beside = Cat
61+
62+||| The document `(nest lvl x)` renders document `x` with the current
63+||| indentation level increased by lvl.
64+nest : (lvl : Int)
65+ -> (x : Doc)
66+ -> Doc
67+nest = Nest
68+
69+column : (f : Int -> Doc) -> Doc
70+column = Column
71+
72+nesting : (f : Int -> Doc) -> Doc
73+nesting = Nesting
74+
75+private %inline
76+flatten : Doc -> Doc
77+flatten (Line break) = if break then Empty else Text 1 " "
78+
79+flatten (Cat x y) = Cat (flatten x) (flatten y)
80+flatten (Nest lvl x) = Nest lvl (flatten x)
81+flatten (Union x _) = flatten x
82+flatten (Column f) = Column (\i => flatten $ f i)
83+flatten (Nesting f) = Nesting (\i => flatten $ f i)
84+
85+flatten other = other
86+
87+||| The `group` combinator is used to specify alternative
88+||| layouts. The document `(group x)` undoes all line breaks in
89+||| document `x`. The resulting line is added to the current line if
90+||| that fits the page. Otherwise, the document `x` is rendered without
91+||| any changes.
92+group : Doc -> Doc
93+group x = Union (flatten x) x
94+
95+Semigroup Doc where
96+ (<+>) x y = beside x y
97+
98+Monoid Doc where
99+ neutral = empty
100+
101+fold : (f : Doc -> Doc -> Doc) -> (ds : List Doc) -> Doc
102+fold _ Nil = empty
103+fold f (x::xs) = foldl f x xs
104+
105+-- --------------------------------------------------------------- [ Renderers ]
106+
107+namespace PrettyDoc
108+
109+ ||| The data type `PrettyDoc` represents rendered documents and is
110+ ||| used by the display functions.
111+ data PrettyDoc : Type where
112+ Empty : PrettyDoc
113+ Chara : (c : Char) -> (rest : PrettyDoc) -> PrettyDoc
114+ Text : (len : Int) -> (str : String) -> (rest : PrettyDoc) -> PrettyDoc
115+ Line : (lvl : Int) -> (rest : PrettyDoc) -> PrettyDoc
116+
117+||| Helper function to construct spaces.
118+spaces : (n : Int) -> String
119+spaces n = if n <= 0 then "" else pack $ replicate (cast n) ' '
120+
121+private
122+indentation : Int -> String
123+indentation n = spaces n
124+
125+||| `(showPrettyDoc doc)` takes the output `PrettyDoc` from a
126+||| rendering function and transforms it to a String for printing.
127+showPrettyDoc : (doc : PrettyDoc) -> String
128+showPrettyDoc doc = showPrettyDocS doc ""
129+ where
130+ showPrettyDocS : (doc : PrettyDoc) -> (acc : String) -> String
131+ showPrettyDocS Empty = id
132+ showPrettyDocS (Chara c rest) = strCons c . showPrettyDocS rest
133+ showPrettyDocS (Text len str rest) = (str ++) . showPrettyDocS rest
134+ showPrettyDocS (Line lvl rest) = (('\n' `strCons` indentation lvl) ++) . showPrettyDocS rest
135+
136+private
137+fits : (w : Int) -> (x : PrettyDoc) -> Bool
138+fits w Empty = if w < 0 then False else True
139+fits w (Chara c rest) = if w < 0 then False else fits (w - 1) rest
140+
141+fits w (Text len str rest) = if w < 0 then False else fits (w - len) rest
142+
143+fits w (Line _ _) = if w < 0 then False else True
144+
145+
146+||| Use the Wadler-Leijen algorithm to pretty print the `doc`.
147+||| The algorithm uses a page width of `width` and a ribbon
148+||| width of `(ribbonfrac * width)` characters. The ribbon width is
149+||| the maximal amount of non-indentation characters on a line. The
150+||| parameter `ribbonfrac` should be between `0.0` and `1.0`. If it is
151+||| lower or higher, the ribbon width will be 0 or `width`
152+||| respectively.
153+render : (rfrac : Double)
154+ -> (width : Int)
155+ -> (doc : Doc)
156+ -> PrettyDoc
157+render rfrac w doc = best w 0 0 doc (const Empty)
158+ where
159+ rwidth : Int
160+ rwidth = max 0 $ min w (cast (cast w * rfrac))
161+
162+ nicest : (lvl : Int)
163+ -> (col : Int)
164+ -> (x : PrettyDoc)
165+ -> (y : PrettyDoc)
166+ -> PrettyDoc
167+ nicest n k x y =
168+ let width = min (w - k) (rwidth - k + n)
169+ in if fits width x then x else y
170+
171+ best : (lvl : Int)
172+ -> (col : Int)
173+ -> (curr : Int)
174+ -> (doc : Doc)
175+ -> (k : Int -> PrettyDoc)
176+ -> PrettyDoc
177+ best lvl col curr Empty k = k col
178+ best lvl col curr (Chara c) k = Chara c $ k (col + 1)
179+ best lvl col curr (Text len str) k = Text len str $ k (col + len)
180+ best lvl col curr (Line undone) k = Line curr $ k curr
181+
182+ best lvl col curr (Cat x y) k =
183+ best lvl col curr x (\curr' => best lvl curr' curr y k)
184+
185+ best lvl col curr (Nest x y) k = best lvl col (curr+x) y k
186+
187+ best lvl col curr (Union x y) k =
188+ nicest lvl col (best lvl col curr x k)
189+ (best lvl col curr y k)
190+
191+ best lvl col curr (Column f) k = best lvl col curr (f col) k
192+ best lvl col curr (Nesting f) k = best lvl col curr (f curr) k
193+
194+||| Print the document to a string using the given ribbon fraction
195+||| and page width.
196+toString : (rfrac : Double)
197+ -> (width : Int)
198+ -> (doc : Doc)
199+ -> String
200+toString rfrac width doc = showPrettyDoc (render rfrac width doc)
201+
202+writeDoc : (fname : String)
203+ -> (rfrac : Double)
204+ -> (width : Int)
205+ -> (doc : Doc)
206+ -> IO (Either FileError ())
207+writeDoc fname rfrac width doc = writeFile fname $ toString rfrac width doc
208+
209+namespace Default
210+
211+ ||| Render the document with a default ribbon fraction of 0.4 and page width of 120 characters.
212+ render : (doc : Doc) -> PrettyDoc
213+ render = render 0.4 120
214+
215+ ||| Print the document to a string using a default ribbon fraction
216+ ||| of 0.4 and page width of 120 characters.
217+ toString : (doc : Doc) -> String
218+ toString = toString 0.4 120
219+
220+ ||| Write the doc to `fname` using a default ribbon fraction of 0.4
221+ ||| and page width of 120 characters.
222+ writeDoc : (fname : String)
223+ -> (doc : Doc)
224+ -> IO (Either FileError ())
225+ writeDoc fname doc = writeFile fname $ Default.toString doc
libs/contrib/contrib.ipkg+5
@@ -32,4 +32,9 @@ modules = CFFI, CFFI.Types, CFFI.Memory,
32
32
33 Text.Lexer, Text.Parser,
33 Text.Lexer, Text.Parser,
34
34
35+ Text.PrettyPrint.WL,
36+ Text.PrettyPrint.WL.Core,
37+ Text.PrettyPrint.WL.Characters,
38+ Text.PrettyPrint.WL.Combinators,
39+
35 Data.Fin.Extra
40 Data.Fin.Extra