1 | #!/usr/bin/env python2
|
2 | from __future__ import print_function
|
3 | """
|
4 | Base class for pretty printing, and HNodeEncoder
|
5 | """
|
6 |
|
7 | from _devbuild.gen.hnode_asdl import hnode, hnode_e, hnode_t, Field, color_e
|
8 | from _devbuild.gen.pretty_asdl import (doc, MeasuredDoc, Measure)
|
9 | from data_lang import j8_lite
|
10 | from display import ansi
|
11 | from display import pretty
|
12 | from display.pretty import (_Break, _Concat, _Flat, _Group, _IfFlat, _Indent,
|
13 | _EmptyMeasure, AsciiText)
|
14 | from mycpp import mylib
|
15 | from mycpp.mylib import log, tagswitch, switch
|
16 | from typing import cast, List, Dict, Optional
|
17 |
|
18 | _ = log
|
19 |
|
20 |
|
21 | class BaseEncoder(object):
|
22 |
|
23 | def __init__(self):
|
24 | # type: () -> None
|
25 |
|
26 | # Default values
|
27 | self.indent = 4
|
28 | self.use_styles = True
|
29 | # Tuned for 'data_lang/pretty-benchmark.sh float-demo'
|
30 | # TODO: might want options for float width
|
31 | self.max_tabular_width = 22
|
32 |
|
33 | self.visiting = {} # type: Dict[int, bool]
|
34 |
|
35 | def SetIndent(self, indent):
|
36 | # type: (int) -> None
|
37 | """Set the number of spaces per indent."""
|
38 | self.indent = indent
|
39 |
|
40 | def SetUseStyles(self, use_styles):
|
41 | # type: (bool) -> None
|
42 | """Print with ansi colors and styles, rather than plain text."""
|
43 | self.use_styles = use_styles
|
44 |
|
45 | def SetMaxTabularWidth(self, max_tabular_width):
|
46 | # type: (int) -> None
|
47 | """Set the maximum width that list elements can be, for them to be
|
48 | vertically aligned."""
|
49 | self.max_tabular_width = max_tabular_width
|
50 |
|
51 | def _Styled(self, style, mdoc):
|
52 | # type: (str, MeasuredDoc) -> MeasuredDoc
|
53 | """Apply the ANSI style string to the given node, if use_styles is set."""
|
54 | if self.use_styles:
|
55 | return _Concat([
|
56 | MeasuredDoc(doc.Text(style), _EmptyMeasure()), mdoc,
|
57 | MeasuredDoc(doc.Text(ansi.RESET), _EmptyMeasure())
|
58 | ])
|
59 | else:
|
60 | return mdoc
|
61 |
|
62 | def _StyledAscii(self, style, s):
|
63 | # type: (str, str) -> MeasuredDoc
|
64 | """Apply the ANSI style string to the given node, if use_styles is set."""
|
65 | measure = Measure(len(s), -1) # like AsciiText()
|
66 | if self.use_styles:
|
67 | s = '%s%s%s' % (style, s, ansi.RESET)
|
68 | return MeasuredDoc(doc.Text(s), measure)
|
69 |
|
70 | def _Surrounded(self, left, mdoc, right):
|
71 | # type: (str, MeasuredDoc, str) -> MeasuredDoc
|
72 | """Print one of two options (using '[', ']' for left, right):
|
73 |
|
74 | [mdoc]
|
75 | ------
|
76 | [
|
77 | mdoc
|
78 | ]
|
79 | """
|
80 | # TODO:
|
81 | # - left and right AsciiText often CONSTANT mdocs
|
82 | # - _Break too
|
83 | return _Group(
|
84 | _Concat([
|
85 | AsciiText(left),
|
86 | _Indent(self.indent, _Concat([_Break(''), mdoc])),
|
87 | _Break(''),
|
88 | AsciiText(right)
|
89 | ]))
|
90 |
|
91 | def _SurroundedAndPrefixed(self, left, prefix, sep, mdoc, right):
|
92 | # type: (str, MeasuredDoc, str, MeasuredDoc, str) -> MeasuredDoc
|
93 | """Print one of two options
|
94 | (using '[', 'prefix', ':', 'mdoc', ']' for left, prefix, sep, mdoc, right):
|
95 |
|
96 | [prefix:mdoc]
|
97 | ------
|
98 | [prefix
|
99 | mdoc
|
100 | ]
|
101 | """
|
102 | return _Group(
|
103 | _Concat([
|
104 | AsciiText(left), prefix,
|
105 | _Indent(self.indent, _Concat([_Break(sep), mdoc])),
|
106 | _Break(''),
|
107 | AsciiText(right)
|
108 | ]))
|
109 |
|
110 | def _Join(self, items, sep, space):
|
111 | # type: (List[MeasuredDoc], str, str) -> MeasuredDoc
|
112 | """Join `items`, using either 'sep+space' or 'sep+newline' between them.
|
113 |
|
114 | e.g., if sep and space are ',' and '_', print one of these two cases:
|
115 |
|
116 | first,_second,_third
|
117 | ------
|
118 | first,
|
119 | second,
|
120 | third
|
121 | """
|
122 | seq = [] # type: List[MeasuredDoc]
|
123 | for i, item in enumerate(items):
|
124 | if i != 0:
|
125 | seq.append(AsciiText(sep))
|
126 | seq.append(_Break(space))
|
127 | seq.append(item)
|
128 | return _Concat(seq)
|
129 |
|
130 | def _Tabular(self, items, sep):
|
131 | # type: (List[MeasuredDoc], str) -> MeasuredDoc
|
132 | """Join `items` together, using one of three styles:
|
133 |
|
134 | (showing spaces as underscores for clarity)
|
135 |
|
136 | first,_second,_third,_fourth,_fifth,_sixth,_seventh,_eighth
|
137 | ------
|
138 | first,___second,__third,
|
139 | fourth,__fifth,___sixth,
|
140 | seventh,_eighth
|
141 | ------
|
142 | first,
|
143 | second,
|
144 | third,
|
145 | fourth,
|
146 | fifth,
|
147 | sixth,
|
148 | seventh,
|
149 | eighth
|
150 |
|
151 | The first "single line" style is used if the items fit on one line. The
|
152 | second "tabular" style is used if the flat width of all items is no
|
153 | greater than self.max_tabular_width. The third "multi line" style is
|
154 | used otherwise.
|
155 | """
|
156 | # Why not "just" use tabular alignment so long as two items fit on every
|
157 | # line? Because it isn't possible to check for that in the pretty
|
158 | # printing language. There are two sorts of conditionals we can do:
|
159 | #
|
160 | # A. Inside the pretty printing language, which supports exactly one
|
161 | # conditional: "does it fit on one line?".
|
162 | # B. Outside the pretty printing language we can run arbitrary Python
|
163 | # code, but we don't know how much space is available on the line
|
164 | # because it depends on the context in which we're printed, which may
|
165 | # vary.
|
166 | #
|
167 | # We're picking between the three styles, by using (A) to check if the
|
168 | # first style fits on one line, then using (B) with "are all the items
|
169 | # smaller than self.max_tabular_width?" to pick between style 2 and
|
170 | # style 3.
|
171 |
|
172 | if len(items) == 0:
|
173 | # TODO: this should be turned into "append nothing"
|
174 | return AsciiText('')
|
175 |
|
176 | max_flat_len = 0
|
177 | seq = [] # type: List[MeasuredDoc]
|
178 | for i, item in enumerate(items):
|
179 | if i != 0:
|
180 | seq.append(AsciiText(sep))
|
181 | seq.append(_Break(' '))
|
182 | # It would be nice if we could extend other _Concat() nodes here
|
183 | seq.append(item)
|
184 |
|
185 | max_flat_len = max(max_flat_len, item.measure.flat)
|
186 |
|
187 | non_tabular = _Concat(seq)
|
188 |
|
189 | #log('MAX FLAT %d', max_flat_len)
|
190 |
|
191 | sep_width = len(sep)
|
192 | if max_flat_len + sep_width + 1 <= self.max_tabular_width:
|
193 | tabular_seq = [] # type: List[MeasuredDoc]
|
194 | for i, item in enumerate(items):
|
195 | tabular_seq.append(_Flat(item))
|
196 | if i != len(items) - 1:
|
197 | padding = max_flat_len - item.measure.flat + 1
|
198 | tabular_seq.append(AsciiText(sep))
|
199 | tabular_seq.append(_Group(_Break(' ' * padding)))
|
200 | tabular = _Concat(tabular_seq)
|
201 | return _Group(_IfFlat(non_tabular, tabular))
|
202 | else:
|
203 | return non_tabular
|
204 |
|
205 |
|
206 | class HNodeEncoder(BaseEncoder):
|
207 |
|
208 | def __init__(self):
|
209 | # type: () -> None
|
210 | BaseEncoder.__init__(self)
|
211 |
|
212 | self.type_color = ansi.YELLOW
|
213 | self.field_color = ansi.MAGENTA
|
214 |
|
215 | def HNode(self, h):
|
216 | # type: (hnode_t) -> MeasuredDoc
|
217 | self.visiting.clear()
|
218 | return self._HNode(h)
|
219 |
|
220 | def _Field(self, field):
|
221 | # type: (Field) -> MeasuredDoc
|
222 | name = AsciiText(field.name + ':')
|
223 |
|
224 | # TODO: the _HNode is often a _Concat node, and we could optimize them
|
225 | # together. That means we also have to concatenate their measures.
|
226 | return _Concat([name, self._HNode(field.val)])
|
227 |
|
228 | def _HNode(self, h):
|
229 | # type: (hnode_t) -> MeasuredDoc
|
230 |
|
231 | UP_h = h
|
232 | with tagswitch(h) as case:
|
233 | if case(hnode_e.AlreadySeen):
|
234 | h = cast(hnode.AlreadySeen, UP_h)
|
235 | return pretty.AsciiText('...0x%s' % mylib.hex_lower(h.heap_id))
|
236 |
|
237 | elif case(hnode_e.Leaf):
|
238 | h = cast(hnode.Leaf, UP_h)
|
239 |
|
240 | with switch(h.color) as case2:
|
241 | if case2(color_e.TypeName):
|
242 | color = ansi.YELLOW
|
243 | elif case2(color_e.StringConst):
|
244 | color = ansi.BOLD
|
245 | elif case2(color_e.OtherConst):
|
246 | color = ansi.GREEN
|
247 | elif case2(color_e.External):
|
248 | color = ansi.BOLD + ansi.BLUE
|
249 | elif case2(color_e.UserType):
|
250 | color = ansi.GREEN # Same color as other literals for now
|
251 | else:
|
252 | raise AssertionError()
|
253 |
|
254 | # TODO: what do we do with node.color
|
255 | s = j8_lite.EncodeString(h.s, unquoted_ok=True)
|
256 |
|
257 | # Could be Unicode, but we don't want that dependency right now
|
258 | return self._StyledAscii(color, s)
|
259 | #return self._Styled(color, AsciiText(s))
|
260 |
|
261 | elif case(hnode_e.Array):
|
262 | h = cast(hnode.Array, UP_h)
|
263 |
|
264 | # Reduces Max RSS! Because we build up the trees all at once,
|
265 | # and there's a ton fo garbage.
|
266 | mylib.MaybeCollect()
|
267 |
|
268 | if len(h.children) == 0:
|
269 | return AsciiText('[]')
|
270 | children = [self._HNode(item) for item in h.children]
|
271 | return self._Surrounded('[', self._Tabular(children, ''), ']')
|
272 |
|
273 | elif case(hnode_e.Record):
|
274 | h = cast(hnode.Record, UP_h)
|
275 |
|
276 | type_name = None # type: Optional[MeasuredDoc]
|
277 | if len(h.node_type):
|
278 | type_name = self._StyledAscii(self.type_color, h.node_type)
|
279 | #type_name = self._Styled(self.type_color, AsciiText(h.node_type))
|
280 |
|
281 | mdocs = None # type: Optional[List[MeasuredDoc]]
|
282 | if h.unnamed_fields is not None and len(h.unnamed_fields):
|
283 | mdocs = [self._HNode(item) for item in h.unnamed_fields]
|
284 | elif len(h.fields) != 0:
|
285 | mdocs = [self._Field(field) for field in h.fields]
|
286 |
|
287 | if mdocs is None:
|
288 | m = [AsciiText(h.left)]
|
289 | if type_name is not None: # {}
|
290 | m.append(type_name)
|
291 | m.append(AsciiText(h.right))
|
292 |
|
293 | # e.g. (value.Stdin) with no fields
|
294 | return _Concat(m)
|
295 |
|
296 | # Named or unnamed
|
297 | child = self._Join(mdocs, '', ' ')
|
298 |
|
299 | if type_name is not None:
|
300 | # e.g. (Token id:LitChars col:5)
|
301 | return self._SurroundedAndPrefixed(h.left, type_name, ' ',
|
302 | child, h.right)
|
303 | else:
|
304 | # e.g. <Id.Lit_Chars foo>
|
305 | return self._Surrounded(h.left, child, h.right)
|
306 |
|
307 | else:
|
308 | raise AssertionError()
|
309 |
|
310 |
|
311 | # vim: sw=4
|