wenzelm@36721
|
1 |
/* Title: Pure/General/pretty.scala
|
wenzelm@36721
|
2 |
Author: Makarius
|
wenzelm@36721
|
3 |
|
wenzelm@36725
|
4 |
Generic pretty printing module.
|
wenzelm@36721
|
5 |
*/
|
wenzelm@36721
|
6 |
|
wenzelm@36721
|
7 |
package isabelle
|
wenzelm@36721
|
8 |
|
wenzelm@36721
|
9 |
|
wenzelm@36721
|
10 |
object Pretty
|
wenzelm@36721
|
11 |
{
|
wenzelm@36725
|
12 |
/* markup trees with physical blocks and breaks */
|
wenzelm@36725
|
13 |
|
wenzelm@36721
|
14 |
object Block
|
wenzelm@36721
|
15 |
{
|
wenzelm@36721
|
16 |
def apply(indent: Int, body: List[XML.Tree]): XML.Tree =
|
wenzelm@36721
|
17 |
XML.Elem(Markup.BLOCK, List((Markup.INDENT, indent.toString)), body)
|
wenzelm@36721
|
18 |
|
wenzelm@36721
|
19 |
def unapply(tree: XML.Tree): Option[(Int, List[XML.Tree])] =
|
wenzelm@36721
|
20 |
tree match {
|
wenzelm@36721
|
21 |
case XML.Elem(Markup.BLOCK, List((Markup.INDENT, indent)), body) =>
|
wenzelm@36721
|
22 |
Markup.parse_int(indent) match {
|
wenzelm@36721
|
23 |
case Some(i) => Some((i, body))
|
wenzelm@36721
|
24 |
case None => None
|
wenzelm@36721
|
25 |
}
|
wenzelm@36721
|
26 |
case _ => None
|
wenzelm@36721
|
27 |
}
|
wenzelm@36721
|
28 |
}
|
wenzelm@36721
|
29 |
|
wenzelm@36721
|
30 |
object Break
|
wenzelm@36721
|
31 |
{
|
wenzelm@36721
|
32 |
def apply(width: Int): XML.Tree =
|
wenzelm@36721
|
33 |
XML.Elem(Markup.BREAK, List((Markup.WIDTH, width.toString)), List(XML.Text(" " * width)))
|
wenzelm@36721
|
34 |
|
wenzelm@36721
|
35 |
def unapply(tree: XML.Tree): Option[Int] =
|
wenzelm@36721
|
36 |
tree match {
|
wenzelm@36721
|
37 |
case XML.Elem(Markup.BREAK, List((Markup.WIDTH, width)), _) => Markup.parse_int(width)
|
wenzelm@36721
|
38 |
case _ => None
|
wenzelm@36721
|
39 |
}
|
wenzelm@36721
|
40 |
}
|
wenzelm@36721
|
41 |
|
wenzelm@36727
|
42 |
val FBreak = XML.Text("\n")
|
wenzelm@36725
|
43 |
|
wenzelm@36725
|
44 |
|
wenzelm@36725
|
45 |
/* formatted output */
|
wenzelm@36725
|
46 |
|
wenzelm@36734
|
47 |
private case class Text(tx: List[XML.Tree] = Nil, val pos: Int = 0, val nl: Int = 0)
|
wenzelm@36725
|
48 |
{
|
wenzelm@36734
|
49 |
def newline: Text = copy(tx = FBreak :: tx, pos = 0, nl = nl + 1)
|
wenzelm@36734
|
50 |
def string(s: String): Text = copy(tx = XML.Text(s) :: tx, pos = pos + s.length)
|
wenzelm@36725
|
51 |
def blanks(wd: Int): Text = string(" " * wd)
|
wenzelm@36734
|
52 |
def content: List[XML.Tree] = tx.reverse
|
wenzelm@36725
|
53 |
}
|
wenzelm@36725
|
54 |
|
wenzelm@36725
|
55 |
private def breakdist(trees: List[XML.Tree], after: Int): Int =
|
wenzelm@36725
|
56 |
trees match {
|
wenzelm@36725
|
57 |
case Break(_) :: _ => 0
|
wenzelm@36727
|
58 |
case FBreak :: _ => 0
|
wenzelm@36725
|
59 |
case XML.Elem(_, _, body) :: ts =>
|
wenzelm@36725
|
60 |
(0 /: body)(_ + XML.content_length(_)) + breakdist(ts, after)
|
wenzelm@36725
|
61 |
case XML.Text(s) :: ts => s.length + breakdist(ts, after)
|
wenzelm@36725
|
62 |
case Nil => after
|
wenzelm@36725
|
63 |
}
|
wenzelm@36725
|
64 |
|
wenzelm@36725
|
65 |
private def forcenext(trees: List[XML.Tree]): List[XML.Tree] =
|
wenzelm@36725
|
66 |
trees match {
|
wenzelm@36725
|
67 |
case Nil => Nil
|
wenzelm@36727
|
68 |
case FBreak :: _ => trees
|
wenzelm@36727
|
69 |
case Break(_) :: ts => FBreak :: ts
|
wenzelm@36725
|
70 |
case t :: ts => t :: forcenext(ts)
|
wenzelm@36725
|
71 |
}
|
wenzelm@36725
|
72 |
|
wenzelm@36734
|
73 |
private def standard_format(tree: XML.Tree): List[XML.Tree] =
|
wenzelm@36727
|
74 |
tree match {
|
wenzelm@36734
|
75 |
case XML.Elem(name, atts, body) => List(XML.Elem(name, atts, body.flatMap(standard_format)))
|
wenzelm@36727
|
76 |
case XML.Text(text) =>
|
wenzelm@36727
|
77 |
Library.separate(FBreak,
|
wenzelm@36727
|
78 |
Library.chunks(text).toList.map((s: CharSequence) => XML.Text(s.toString)))
|
wenzelm@36727
|
79 |
}
|
wenzelm@36727
|
80 |
|
wenzelm@36754
|
81 |
private val margin_default = 76
|
wenzelm@36734
|
82 |
|
wenzelm@36754
|
83 |
def formatted(input: List[XML.Tree], margin: Int = margin_default): List[XML.Tree] =
|
wenzelm@36725
|
84 |
{
|
wenzelm@36725
|
85 |
val breakgain = margin / 20
|
wenzelm@36725
|
86 |
val emergencypos = margin / 2
|
wenzelm@36725
|
87 |
|
wenzelm@36725
|
88 |
def format(trees: List[XML.Tree], blockin: Int, after: Int, text: Text): Text =
|
wenzelm@36725
|
89 |
trees match {
|
wenzelm@36725
|
90 |
case Nil => text
|
wenzelm@36725
|
91 |
|
wenzelm@36725
|
92 |
case Block(indent, body) :: ts =>
|
wenzelm@36725
|
93 |
val pos1 = text.pos + indent
|
wenzelm@36725
|
94 |
val pos2 = pos1 % emergencypos
|
wenzelm@36725
|
95 |
val blockin1 =
|
wenzelm@36725
|
96 |
if (pos1 < emergencypos) pos1
|
wenzelm@36725
|
97 |
else pos2
|
wenzelm@36725
|
98 |
val btext = format(body, blockin1, breakdist(ts, after), text)
|
wenzelm@36725
|
99 |
val ts1 = if (text.nl < btext.nl) forcenext(ts) else ts
|
wenzelm@36725
|
100 |
format(ts1, blockin, after, btext)
|
wenzelm@36725
|
101 |
|
wenzelm@36725
|
102 |
case Break(wd) :: ts =>
|
wenzelm@36725
|
103 |
if (text.pos + wd <= (margin - breakdist(ts, after)).max(blockin + breakgain))
|
wenzelm@36725
|
104 |
format(ts, blockin, after, text.blanks(wd))
|
wenzelm@36725
|
105 |
else format(ts, blockin, after, text.newline.blanks(blockin))
|
wenzelm@36727
|
106 |
case FBreak :: ts => format(ts, blockin, after, text.newline.blanks(blockin))
|
wenzelm@36725
|
107 |
|
wenzelm@36734
|
108 |
case XML.Elem(name, atts, body) :: ts =>
|
wenzelm@36734
|
109 |
val btext = format(body, blockin, breakdist(ts, after), text.copy(tx = Nil))
|
wenzelm@36734
|
110 |
val ts1 = if (text.nl < btext.nl) forcenext(ts) else ts
|
wenzelm@36734
|
111 |
val btext1 = btext.copy(tx = XML.Elem(name, atts, btext.content) :: text.tx)
|
wenzelm@36734
|
112 |
format(ts1, blockin, after, btext1)
|
wenzelm@36725
|
113 |
case XML.Text(s) :: ts => format(ts, blockin, after, text.string(s))
|
wenzelm@36725
|
114 |
}
|
wenzelm@36734
|
115 |
format(input.flatMap(standard_format), 0, 0, Text()).content
|
wenzelm@36725
|
116 |
}
|
wenzelm@36734
|
117 |
|
wenzelm@36754
|
118 |
def string_of(input: List[XML.Tree], margin: Int = margin_default): String =
|
wenzelm@36736
|
119 |
formatted(input).iterator.flatMap(XML.content).mkString
|
wenzelm@36736
|
120 |
|
wenzelm@36736
|
121 |
|
wenzelm@36736
|
122 |
/* unformatted output */
|
wenzelm@36736
|
123 |
|
wenzelm@36736
|
124 |
def unformatted(input: List[XML.Tree]): List[XML.Tree] =
|
wenzelm@36736
|
125 |
{
|
wenzelm@36736
|
126 |
def fmt(tree: XML.Tree): List[XML.Tree] =
|
wenzelm@36736
|
127 |
tree match {
|
wenzelm@36736
|
128 |
case Block(_, body) => body.flatMap(fmt)
|
wenzelm@36736
|
129 |
case Break(wd) => List(XML.Text(" " * wd))
|
wenzelm@36736
|
130 |
case FBreak => List(XML.Text(" "))
|
wenzelm@36736
|
131 |
case XML.Elem(name, atts, body) => List(XML.Elem(name, atts, body.flatMap(fmt)))
|
wenzelm@36736
|
132 |
case XML.Text(_) => List(tree)
|
wenzelm@36736
|
133 |
}
|
wenzelm@36736
|
134 |
input.flatMap(standard_format).flatMap(fmt)
|
wenzelm@36736
|
135 |
}
|
wenzelm@36736
|
136 |
|
wenzelm@36736
|
137 |
def str_of(input: List[XML.Tree]): String =
|
wenzelm@36736
|
138 |
unformatted(input).iterator.flatMap(XML.content).mkString
|
wenzelm@36721
|
139 |
}
|