| 1 | """
 | 
| 2 | format.py -- Pretty print an ASDL data structure.
 | 
| 3 | 
 | 
| 4 | TODO: replace ad hoc line wrapper, e.g. _TrySingleLine
 | 
| 5 | 
 | 
| 6 | - auto-abbreviation of single field things (minus location)
 | 
| 7 | - option to omit spaces for SQ, SQ, W?  It's all one thing.
 | 
| 8 | 
 | 
| 9 | Where we try wrap to a single line:
 | 
| 10 |  - arrays
 | 
| 11 |  - objects with name fields
 | 
| 12 |  - abbreviated, unnamed fields
 | 
| 13 | """
 | 
| 14 | from typing import Tuple, List
 | 
| 15 | 
 | 
| 16 | from _devbuild.gen.hnode_asdl import (hnode, hnode_e, hnode_t, color_e,
 | 
| 17 |                                       color_t)
 | 
| 18 | from core import ansi
 | 
| 19 | from data_lang import j8_lite
 | 
| 20 | from pylib import cgi
 | 
| 21 | from mycpp import mylib
 | 
| 22 | 
 | 
| 23 | from typing import cast, Any, Optional
 | 
| 24 | 
 | 
| 25 | if mylib.PYTHON:
 | 
| 26 | 
 | 
| 27 |     def PrettyPrint(obj, f=None):
 | 
| 28 |         # type: (Any, Optional[mylib.Writer]) -> None
 | 
| 29 |         """Print abbreviated tree in color.  For unit tests."""
 | 
| 30 |         f = f if f else mylib.Stdout()
 | 
| 31 | 
 | 
| 32 |         ast_f = DetectConsoleOutput(f)
 | 
| 33 |         tree = obj.AbbreviatedTree()
 | 
| 34 |         PrintTree(tree, ast_f)
 | 
| 35 | 
 | 
| 36 | 
 | 
| 37 | def DetectConsoleOutput(f):
 | 
| 38 |     # type: (mylib.Writer) -> ColorOutput
 | 
| 39 |     """Wrapped to auto-detect."""
 | 
| 40 |     if f.isatty():
 | 
| 41 |         return AnsiOutput(f)
 | 
| 42 |     else:
 | 
| 43 |         return TextOutput(f)
 | 
| 44 | 
 | 
| 45 | 
 | 
| 46 | class ColorOutput(object):
 | 
| 47 |     """Abstract base class for plain text, ANSI color, and HTML color."""
 | 
| 48 | 
 | 
| 49 |     def __init__(self, f):
 | 
| 50 |         # type: (mylib.Writer) -> None
 | 
| 51 |         self.f = f
 | 
| 52 |         self.num_chars = 0
 | 
| 53 | 
 | 
| 54 |     def NewTempBuffer(self):
 | 
| 55 |         # type: () -> ColorOutput
 | 
| 56 |         """Return a temporary buffer for the line wrapping calculation."""
 | 
| 57 |         raise NotImplementedError()
 | 
| 58 | 
 | 
| 59 |     def FileHeader(self):
 | 
| 60 |         # type: () -> None
 | 
| 61 |         """Hook for printing a full file."""
 | 
| 62 |         pass
 | 
| 63 | 
 | 
| 64 |     def FileFooter(self):
 | 
| 65 |         # type: () -> None
 | 
| 66 |         """Hook for printing a full file."""
 | 
| 67 |         pass
 | 
| 68 | 
 | 
| 69 |     def PushColor(self, e_color):
 | 
| 70 |         # type: (color_t) -> None
 | 
| 71 |         raise NotImplementedError()
 | 
| 72 | 
 | 
| 73 |     def PopColor(self):
 | 
| 74 |         # type: () -> None
 | 
| 75 |         raise NotImplementedError()
 | 
| 76 | 
 | 
| 77 |     def write(self, s):
 | 
| 78 |         # type: (str) -> None
 | 
| 79 |         self.f.write(s)
 | 
| 80 |         self.num_chars += len(s)  # Only count visible characters!
 | 
| 81 | 
 | 
| 82 |     def WriteRaw(self, raw):
 | 
| 83 |         # type: (Tuple[str, int]) -> None
 | 
| 84 |         """Write raw data without escaping, and without counting control codes
 | 
| 85 |         in the length."""
 | 
| 86 |         s, num_chars = raw
 | 
| 87 |         self.f.write(s)
 | 
| 88 |         self.num_chars += num_chars
 | 
| 89 | 
 | 
| 90 |     def NumChars(self):
 | 
| 91 |         # type: () -> int
 | 
| 92 |         return self.num_chars
 | 
| 93 | 
 | 
| 94 |     def GetRaw(self):
 | 
| 95 |         # type: () -> Tuple[str, int]
 | 
| 96 | 
 | 
| 97 |         # NOTE: Ensured by NewTempBuffer()
 | 
| 98 |         f = cast(mylib.BufWriter, self.f)
 | 
| 99 |         return f.getvalue(), self.num_chars
 | 
| 100 | 
 | 
| 101 | 
 | 
| 102 | class TextOutput(ColorOutput):
 | 
| 103 |     """TextOutput put obeys the color interface, but outputs nothing."""
 | 
| 104 | 
 | 
| 105 |     def __init__(self, f):
 | 
| 106 |         # type: (mylib.Writer) -> None
 | 
| 107 |         ColorOutput.__init__(self, f)
 | 
| 108 | 
 | 
| 109 |     def NewTempBuffer(self):
 | 
| 110 |         # type: () -> TextOutput
 | 
| 111 |         return TextOutput(mylib.BufWriter())
 | 
| 112 | 
 | 
| 113 |     def PushColor(self, e_color):
 | 
| 114 |         # type: (color_t) -> None
 | 
| 115 |         pass  # ignore color
 | 
| 116 | 
 | 
| 117 |     def PopColor(self):
 | 
| 118 |         # type: () -> None
 | 
| 119 |         pass  # ignore color
 | 
| 120 | 
 | 
| 121 | 
 | 
| 122 | class HtmlOutput(ColorOutput):
 | 
| 123 |     """HTML one can have wider columns.  Maybe not even fixed-width font.  Hm
 | 
| 124 |     yeah indentation should be logical then?
 | 
| 125 | 
 | 
| 126 |     Color: HTML spans
 | 
| 127 |     """
 | 
| 128 | 
 | 
| 129 |     def __init__(self, f):
 | 
| 130 |         # type: (mylib.Writer) -> None
 | 
| 131 |         ColorOutput.__init__(self, f)
 | 
| 132 | 
 | 
| 133 |     def NewTempBuffer(self):
 | 
| 134 |         # type: () -> HtmlOutput
 | 
| 135 |         return HtmlOutput(mylib.BufWriter())
 | 
| 136 | 
 | 
| 137 |     def FileHeader(self):
 | 
| 138 |         # type: () -> None
 | 
| 139 |         # TODO: Use a different CSS file to make the colors match.  I like string
 | 
| 140 |         # literals as yellow, etc.
 | 
| 141 |         #<link rel="stylesheet" type="text/css" href="/css/code.css" />
 | 
| 142 |         self.f.write("""
 | 
| 143 | <html>
 | 
| 144 |   <head>
 | 
| 145 |      <title>oil AST</title>
 | 
| 146 |      <style>
 | 
| 147 |       .n { color: brown }
 | 
| 148 |       .s { font-weight: bold }
 | 
| 149 |       .o { color: darkgreen }
 | 
| 150 |      </style>
 | 
| 151 |   </head>
 | 
| 152 |   <body>
 | 
| 153 |     <pre>
 | 
| 154 | """)
 | 
| 155 | 
 | 
| 156 |     def FileFooter(self):
 | 
| 157 |         # type: () -> None
 | 
| 158 |         self.f.write("""
 | 
| 159 |     </pre>
 | 
| 160 |   </body>
 | 
| 161 | </html>
 | 
| 162 |     """)
 | 
| 163 | 
 | 
| 164 |     def PushColor(self, e_color):
 | 
| 165 |         # type: (color_t) -> None
 | 
| 166 |         # To save bandwidth, use single character CSS names.
 | 
| 167 |         if e_color == color_e.TypeName:
 | 
| 168 |             css_class = 'n'
 | 
| 169 |         elif e_color == color_e.StringConst:
 | 
| 170 |             css_class = 's'
 | 
| 171 |         elif e_color == color_e.OtherConst:
 | 
| 172 |             css_class = 'o'
 | 
| 173 |         elif e_color == color_e.External:
 | 
| 174 |             css_class = 'o'
 | 
| 175 |         elif e_color == color_e.UserType:
 | 
| 176 |             css_class = 'o'
 | 
| 177 |         else:
 | 
| 178 |             raise AssertionError(e_color)
 | 
| 179 |         self.f.write('<span class="%s">' % css_class)
 | 
| 180 | 
 | 
| 181 |     def PopColor(self):
 | 
| 182 |         # type: () -> None
 | 
| 183 |         self.f.write('</span>')
 | 
| 184 | 
 | 
| 185 |     def write(self, s):
 | 
| 186 |         # type: (str) -> None
 | 
| 187 | 
 | 
| 188 |         # PROBLEM: Double escaping!
 | 
| 189 |         self.f.write(cgi.escape(s))
 | 
| 190 |         self.num_chars += len(s)  # Only count visible characters!
 | 
| 191 | 
 | 
| 192 | 
 | 
| 193 | class AnsiOutput(ColorOutput):
 | 
| 194 |     """For the console."""
 | 
| 195 | 
 | 
| 196 |     def __init__(self, f):
 | 
| 197 |         # type: (mylib.Writer) -> None
 | 
| 198 |         ColorOutput.__init__(self, f)
 | 
| 199 | 
 | 
| 200 |     def NewTempBuffer(self):
 | 
| 201 |         # type: () -> AnsiOutput
 | 
| 202 |         return AnsiOutput(mylib.BufWriter())
 | 
| 203 | 
 | 
| 204 |     def PushColor(self, e_color):
 | 
| 205 |         # type: (color_t) -> None
 | 
| 206 |         if e_color == color_e.TypeName:
 | 
| 207 |             self.f.write(ansi.YELLOW)
 | 
| 208 |         elif e_color == color_e.StringConst:
 | 
| 209 |             self.f.write(ansi.BOLD)
 | 
| 210 |         elif e_color == color_e.OtherConst:
 | 
| 211 |             self.f.write(ansi.GREEN)
 | 
| 212 |         elif e_color == color_e.External:
 | 
| 213 |             self.f.write(ansi.BOLD + ansi.BLUE)
 | 
| 214 |         elif e_color == color_e.UserType:
 | 
| 215 |             self.f.write(ansi.GREEN)  # Same color as other literals for now
 | 
| 216 |         else:
 | 
| 217 |             raise AssertionError(e_color)
 | 
| 218 | 
 | 
| 219 |     def PopColor(self):
 | 
| 220 |         # type: () -> None
 | 
| 221 |         self.f.write(ansi.RESET)
 | 
| 222 | 
 | 
| 223 | 
 | 
| 224 | INDENT = 2
 | 
| 225 | 
 | 
| 226 | 
 | 
| 227 | class _PrettyPrinter(object):
 | 
| 228 | 
 | 
| 229 |     def __init__(self, max_col):
 | 
| 230 |         # type: (int) -> None
 | 
| 231 |         self.max_col = max_col
 | 
| 232 | 
 | 
| 233 |     def _PrintWrappedArray(self, array, prefix_len, f, indent):
 | 
| 234 |         # type: (List[hnode_t], int, ColorOutput, int) -> bool
 | 
| 235 |         """Print an array of objects with line wrapping.
 | 
| 236 | 
 | 
| 237 |         Returns whether they all fit on a single line, so you can print
 | 
| 238 |         the closing brace properly.
 | 
| 239 |         """
 | 
| 240 |         all_fit = True
 | 
| 241 |         chars_so_far = prefix_len
 | 
| 242 | 
 | 
| 243 |         for i, val in enumerate(array):
 | 
| 244 |             if i != 0:
 | 
| 245 |                 f.write(' ')
 | 
| 246 | 
 | 
| 247 |             single_f = f.NewTempBuffer()
 | 
| 248 |             if _TrySingleLine(val, single_f, self.max_col - chars_so_far):
 | 
| 249 |                 s, num_chars = single_f.GetRaw()  # extra unpacking for mycpp
 | 
| 250 |                 f.WriteRaw((s, num_chars))
 | 
| 251 |                 chars_so_far += single_f.NumChars()
 | 
| 252 |             else:  # WRAP THE LINE
 | 
| 253 |                 f.write('\n')
 | 
| 254 |                 self.PrintNode(val, f, indent + INDENT)
 | 
| 255 | 
 | 
| 256 |                 chars_so_far = 0  # allow more
 | 
| 257 |                 all_fit = False
 | 
| 258 |         return all_fit
 | 
| 259 | 
 | 
| 260 |     def _PrintWholeArray(self, array, prefix_len, f, indent):
 | 
| 261 |         # type: (List[hnode_t], int, ColorOutput, int) -> bool
 | 
| 262 | 
 | 
| 263 |         # This is UNLIKE the abbreviated case above, where we do WRAPPING.
 | 
| 264 |         # Here, ALL children must fit on a single line, or else we separate
 | 
| 265 |         # each one onto a separate line.  This is to avoid the following:
 | 
| 266 |         #
 | 
| 267 |         # children: [(C ...)
 | 
| 268 |         #   (C ...)
 | 
| 269 |         # ]
 | 
| 270 |         # The first child is out of line.  The abbreviated objects have a
 | 
| 271 |         # small header like C or DQ so it doesn't matter as much.
 | 
| 272 |         all_fit = True
 | 
| 273 |         pieces = []  # type: List[Tuple[str, int]]
 | 
| 274 |         chars_so_far = prefix_len
 | 
| 275 |         for item in array:
 | 
| 276 |             single_f = f.NewTempBuffer()
 | 
| 277 |             if _TrySingleLine(item, single_f, self.max_col - chars_so_far):
 | 
| 278 |                 s, num_chars = single_f.GetRaw()  # extra unpacking for mycpp
 | 
| 279 |                 pieces.append((s, num_chars))
 | 
| 280 |                 chars_so_far += single_f.NumChars()
 | 
| 281 |             else:
 | 
| 282 |                 all_fit = False
 | 
| 283 |                 break
 | 
| 284 | 
 | 
| 285 |         if all_fit:
 | 
| 286 |             for i, p in enumerate(pieces):
 | 
| 287 |                 if i != 0:
 | 
| 288 |                     f.write(' ')
 | 
| 289 |                 f.WriteRaw(p)
 | 
| 290 |             f.write(']')
 | 
| 291 |         return all_fit
 | 
| 292 | 
 | 
| 293 |     def _PrintRecord(self, node, f, indent):
 | 
| 294 |         # type: (hnode.Record, ColorOutput, int) -> None
 | 
| 295 |         """Print a CompoundObj in abbreviated or normal form."""
 | 
| 296 |         ind = ' ' * indent
 | 
| 297 | 
 | 
| 298 |         if node.abbrev:  # abbreviated
 | 
| 299 |             prefix = ind + node.left
 | 
| 300 |             f.write(prefix)
 | 
| 301 |             if len(node.node_type):
 | 
| 302 |                 f.PushColor(color_e.TypeName)
 | 
| 303 |                 f.write(node.node_type)
 | 
| 304 |                 f.PopColor()
 | 
| 305 |                 f.write(' ')
 | 
| 306 | 
 | 
| 307 |             prefix_len = len(prefix) + len(node.node_type) + 1
 | 
| 308 |             all_fit = self._PrintWrappedArray(node.unnamed_fields, prefix_len,
 | 
| 309 |                                               f, indent)
 | 
| 310 | 
 | 
| 311 |             if not all_fit:
 | 
| 312 |                 f.write('\n')
 | 
| 313 |                 f.write(ind)
 | 
| 314 |             f.write(node.right)
 | 
| 315 | 
 | 
| 316 |         else:  # full form like (SimpleCommand ...)
 | 
| 317 |             f.write(ind + node.left)
 | 
| 318 | 
 | 
| 319 |             f.PushColor(color_e.TypeName)
 | 
| 320 |             f.write(node.node_type)
 | 
| 321 |             f.PopColor()
 | 
| 322 | 
 | 
| 323 |             f.write('\n')
 | 
| 324 |             for field in node.fields:
 | 
| 325 |                 name = field.name
 | 
| 326 |                 val = field.val
 | 
| 327 | 
 | 
| 328 |                 ind1 = ' ' * (indent + INDENT)
 | 
| 329 |                 UP_val = val  # for mycpp
 | 
| 330 |                 tag = val.tag()
 | 
| 331 |                 if tag == hnode_e.Array:
 | 
| 332 |                     val = cast(hnode.Array, UP_val)
 | 
| 333 | 
 | 
| 334 |                     name_str = '%s%s: [' % (ind1, name)
 | 
| 335 |                     f.write(name_str)
 | 
| 336 |                     prefix_len = len(name_str)
 | 
| 337 | 
 | 
| 338 |                     if not self._PrintWholeArray(val.children, prefix_len, f,
 | 
| 339 |                                                  indent):
 | 
| 340 |                         f.write('\n')
 | 
| 341 |                         for child in val.children:
 | 
| 342 |                             self.PrintNode(child, f, indent + INDENT + INDENT)
 | 
| 343 |                             f.write('\n')
 | 
| 344 |                         f.write('%s]' % ind1)
 | 
| 345 | 
 | 
| 346 |                 else:  # primitive field
 | 
| 347 |                     name_str = '%s%s: ' % (ind1, name)
 | 
| 348 |                     f.write(name_str)
 | 
| 349 |                     prefix_len = len(name_str)
 | 
| 350 | 
 | 
| 351 |                     # Try to print it on the same line as the field name; otherwise print
 | 
| 352 |                     # it on a separate line.
 | 
| 353 |                     single_f = f.NewTempBuffer()
 | 
| 354 |                     if _TrySingleLine(val, single_f,
 | 
| 355 |                                       self.max_col - prefix_len):
 | 
| 356 |                         s, num_chars = single_f.GetRaw(
 | 
| 357 |                         )  # extra unpacking for mycpp
 | 
| 358 |                         f.WriteRaw((s, num_chars))
 | 
| 359 |                     else:
 | 
| 360 |                         f.write('\n')
 | 
| 361 |                         self.PrintNode(val, f, indent + INDENT + INDENT)
 | 
| 362 | 
 | 
| 363 |                 f.write('\n')  # separate fields
 | 
| 364 | 
 | 
| 365 |             f.write(ind + node.right)
 | 
| 366 | 
 | 
| 367 |     def PrintNode(self, node, f, indent):
 | 
| 368 |         # type: (hnode_t, ColorOutput, int) -> None
 | 
| 369 |         """Second step of printing: turn homogeneous tree into a colored
 | 
| 370 |         string.
 | 
| 371 | 
 | 
| 372 |         Args:
 | 
| 373 |           node: homogeneous tree node
 | 
| 374 |           f: ColorOutput instance.
 | 
| 375 |           max_col: don't print past this column number on ANY line
 | 
| 376 |             NOTE: See asdl/run.sh line-length-hist for a test of this.  It's
 | 
| 377 |             approximate.
 | 
| 378 |             TODO: Use the terminal width.
 | 
| 379 |         """
 | 
| 380 |         ind = ' ' * indent
 | 
| 381 | 
 | 
| 382 |         # Try printing on a single line
 | 
| 383 |         single_f = f.NewTempBuffer()
 | 
| 384 |         single_f.write(ind)
 | 
| 385 |         if _TrySingleLine(node, single_f, self.max_col - indent):
 | 
| 386 |             s, num_chars = single_f.GetRaw()  # extra unpacking for mycpp
 | 
| 387 |             f.WriteRaw((s, num_chars))
 | 
| 388 |             return
 | 
| 389 | 
 | 
| 390 |         UP_node = node  # for mycpp
 | 
| 391 |         tag = node.tag()
 | 
| 392 |         if tag == hnode_e.Leaf:
 | 
| 393 |             node = cast(hnode.Leaf, UP_node)
 | 
| 394 |             f.PushColor(node.color)
 | 
| 395 |             f.write(j8_lite.EncodeString(node.s, unquoted_ok=True))
 | 
| 396 |             f.PopColor()
 | 
| 397 | 
 | 
| 398 |         elif tag == hnode_e.External:
 | 
| 399 |             node = cast(hnode.External, UP_node)
 | 
| 400 |             f.PushColor(color_e.External)
 | 
| 401 |             if mylib.PYTHON:
 | 
| 402 |                 f.write(repr(node.obj))
 | 
| 403 |             else:
 | 
| 404 |                 f.write('UNTYPED any')
 | 
| 405 |             f.PopColor()
 | 
| 406 | 
 | 
| 407 |         elif tag == hnode_e.Record:
 | 
| 408 |             node = cast(hnode.Record, UP_node)
 | 
| 409 |             self._PrintRecord(node, f, indent)
 | 
| 410 | 
 | 
| 411 |         elif tag == hnode_e.AlreadySeen:
 | 
| 412 |             node = cast(hnode.AlreadySeen, UP_node)
 | 
| 413 |             # ... means omitting second reference, while --- means a cycle
 | 
| 414 |             f.write('...0x%s' % mylib.hex_lower(node.heap_id))
 | 
| 415 | 
 | 
| 416 |         else:
 | 
| 417 |             raise AssertionError(node)
 | 
| 418 | 
 | 
| 419 | 
 | 
| 420 | def _TrySingleLineObj(node, f, max_chars):
 | 
| 421 |     # type: (hnode.Record, ColorOutput, int) -> bool
 | 
| 422 |     """Print an object on a single line."""
 | 
| 423 |     f.write(node.left)
 | 
| 424 |     if node.abbrev:
 | 
| 425 |         if len(node.node_type):
 | 
| 426 |             f.PushColor(color_e.TypeName)
 | 
| 427 |             f.write(node.node_type)
 | 
| 428 |             f.PopColor()
 | 
| 429 |             f.write(' ')
 | 
| 430 | 
 | 
| 431 |         for i, val in enumerate(node.unnamed_fields):
 | 
| 432 |             if i != 0:
 | 
| 433 |                 f.write(' ')
 | 
| 434 |             if not _TrySingleLine(val, f, max_chars):
 | 
| 435 |                 return False
 | 
| 436 |     else:
 | 
| 437 |         f.PushColor(color_e.TypeName)
 | 
| 438 |         f.write(node.node_type)
 | 
| 439 |         f.PopColor()
 | 
| 440 | 
 | 
| 441 |         for field in node.fields:
 | 
| 442 |             f.write(' %s:' % field.name)
 | 
| 443 |             if not _TrySingleLine(field.val, f, max_chars):
 | 
| 444 |                 return False
 | 
| 445 | 
 | 
| 446 |     f.write(node.right)
 | 
| 447 |     return True
 | 
| 448 | 
 | 
| 449 | 
 | 
| 450 | def _TrySingleLine(node, f, max_chars):
 | 
| 451 |     # type: (hnode_t, ColorOutput, int) -> bool
 | 
| 452 |     """Try printing on a single line.
 | 
| 453 | 
 | 
| 454 |     Args:
 | 
| 455 |       node: homogeneous tree node
 | 
| 456 |       f: ColorOutput instance
 | 
| 457 |       max_chars: maximum number of characters to print on THIS line
 | 
| 458 |       indent: current indent level
 | 
| 459 | 
 | 
| 460 |     Returns:
 | 
| 461 |       ok: whether it fit on the line of the given size.
 | 
| 462 |         If False, you can't use the value of f.
 | 
| 463 |     """
 | 
| 464 |     UP_node = node  # for mycpp
 | 
| 465 |     tag = node.tag()
 | 
| 466 |     if tag == hnode_e.Leaf:
 | 
| 467 |         node = cast(hnode.Leaf, UP_node)
 | 
| 468 |         f.PushColor(node.color)
 | 
| 469 |         f.write(j8_lite.EncodeString(node.s, unquoted_ok=True))
 | 
| 470 |         f.PopColor()
 | 
| 471 | 
 | 
| 472 |     elif tag == hnode_e.External:
 | 
| 473 |         node = cast(hnode.External, UP_node)
 | 
| 474 | 
 | 
| 475 |         f.PushColor(color_e.External)
 | 
| 476 |         if mylib.PYTHON:
 | 
| 477 |             f.write(repr(node.obj))
 | 
| 478 |         else:
 | 
| 479 |             f.write('UNTYPED any')
 | 
| 480 |         f.PopColor()
 | 
| 481 | 
 | 
| 482 |     elif tag == hnode_e.Array:
 | 
| 483 |         node = cast(hnode.Array, UP_node)
 | 
| 484 | 
 | 
| 485 |         # Can we fit the WHOLE array on the line?
 | 
| 486 |         f.write('[')
 | 
| 487 |         for i, item in enumerate(node.children):
 | 
| 488 |             if i != 0:
 | 
| 489 |                 f.write(' ')
 | 
| 490 |             if not _TrySingleLine(item, f, max_chars):
 | 
| 491 |                 return False
 | 
| 492 |         f.write(']')
 | 
| 493 | 
 | 
| 494 |     elif tag == hnode_e.Record:
 | 
| 495 |         node = cast(hnode.Record, UP_node)
 | 
| 496 | 
 | 
| 497 |         return _TrySingleLineObj(node, f, max_chars)
 | 
| 498 | 
 | 
| 499 |     elif tag == hnode_e.AlreadySeen:
 | 
| 500 |         node = cast(hnode.AlreadySeen, UP_node)
 | 
| 501 |         # ... means omitting second reference, while --- means a cycle
 | 
| 502 |         f.write('...0x%s' % mylib.hex_lower(node.heap_id))
 | 
| 503 | 
 | 
| 504 |     else:
 | 
| 505 |         raise AssertionError(node)
 | 
| 506 | 
 | 
| 507 |     # Take into account the last char.
 | 
| 508 |     num_chars_so_far = f.NumChars()
 | 
| 509 |     if num_chars_so_far > max_chars:
 | 
| 510 |         return False
 | 
| 511 | 
 | 
| 512 |     return True
 | 
| 513 | 
 | 
| 514 | 
 | 
| 515 | def PrintTree(node, f):
 | 
| 516 |     # type: (hnode_t, ColorOutput) -> None
 | 
| 517 |     pp = _PrettyPrinter(100)  # max_col
 | 
| 518 |     pp.PrintNode(node, f, 0)  # indent
 |