OILS / data_lang / pretty.py View on Github | oilshell.org

745 lines, 355 significant
1#!/usr/bin/env python2
2"""
3Pretty print Oils values (and later other data/languages as well).
4
5Pretty printing means intelligently choosing whitespace including indentation
6and newline placement, to attempt to display data nicely while staying within a
7maximum line width.
8"""
9
10# ~~~ Architecture ~~~
11#
12# Based on a version of the algorithm from Wadler's "A Prettier Printer".
13#
14# Pretty printing proceeds in two phases:
15#
16# 1. Convert the thing you want to print into a `doc`.
17# 2. Print the `doc` using a standard algorithm.
18#
19# This separation keeps the details of the data you want to print separate from
20# the printing algorithm.
21
22# ~~~ Pretty Printing Overview ~~~
23#
24# If you're just using this file, you don't need to know how pretty printing
25# works. Just call `PrettyPrinter().PrintValue()`. However if you want to change
26# or extend how values are printed, you'll need to know, so here's an overview.
27#
28# You may want to first read Walder's "A Prettier Printer", which this is based
29# off of:
30# https://homepages.inf.ed.ac.uk/wadler/papers/prettier/prettier.pdf
31#
32# Some additional reading, though only tangentially related:
33#
34# - https://homepages.inf.ed.ac.uk/wadler/papers/prettier/prettier.pdf
35# - https://lindig.github.io/papers/strictly-pretty-2000.pdf
36# - https://justinpombrio.net/2024/02/23/a-twist-on-Wadlers-printer.html
37# - https://lobste.rs/s/1r0aak/twist_on_wadler_s_printer
38# - https://lobste.rs/s/aevptj/why_is_prettier_rock_solid
39#
40# ~ Constructors ~
41#
42# There are just a few constructors for `doc`, from which everything else is
43# built from.
44#
45# Text(string) prints like:
46# |string
47#
48# Break(string) prints like:
49# |string
50# or like a newline:
51# |
52# |
53# (It does the latter if printed in "flat" mode, and the former otherwise. See
54# Group for details.)
55#
56# Concat(a, b) prints like:
57# |AAAAA
58# |AAABBB
59# |BBBBB
60#
61# Indent(3, a) prints like:
62# |AAAAA
63# | AAAAA
64# | AAAAA
65#
66# Group(a) makes a decision. It either:
67# - Prints `a` "flat", meaning that (i) every Break inside of it is printed as a
68# string instead of as a newline, and (ii) every Group nested inside of it is
69# printed flat.
70# - Prints `a` normally, meaning that (i) the Breaks inside of it are printed as
71# newlines, and (ii) the Groups inside of it make their own decision about
72# whether to be flat.
73# It makes this decision greedily. If the current line would not overflow if the
74# group printed flat, then it will print flat. This takes into account not only
75# the group itself, but the content before and after it on the same line.
76#
77# IfFlat(a, b) prints a if in flat mode or b otherwise.
78#
79# Flat(a) prints a in flat mode. You should generally not need to use it.
80#
81# ~ Measures ~
82#
83# The algorithm used here is close to the one originally described by Wadler,
84# but it precomputes a "measure" for each node in the `doc`. This "measure"
85# allows each Groups to decide whether to print flat or not without needing to
86# look ahead per Wadler's algorithm. A measure has two pieces of information:
87#
88# - Measure.flat is the width of the doc if it's printed flat.
89# - Measure.nonflat is the width of the doc until the _earliest possible_
90# newline, or -1 if it doesn't contain a Break.
91#
92# Measures are used in two steps:
93# (1) First, they're computed bottom-up on the `doc`, measuring the size of each
94# node.
95# (2) Later, PrintDoc() stores a measure in each DocFragment. These Measures
96# measure something different: the width from the doc _to the end of the
97# entire doc tree_. This second set of Measures (the ones in the
98# DocFragments) are computed top-down, and they're used to decide for each
99# Group whether to use flat mode or not, without needing to scan ahead.
100
101from __future__ import print_function
102
103import math
104
105from _devbuild.gen.pretty_asdl import doc, doc_e, DocFragment, Measure, MeasuredDoc
106from _devbuild.gen.value_asdl import value, value_e, value_t, value_str
107from data_lang import j8
108from data_lang import j8_lite
109from core import ansi
110from frontend import match
111from mycpp import mops
112from mycpp.mylib import log, tagswitch, BufWriter, iteritems
113from typing import cast, List, Dict
114import libc
115
116_ = log
117
118
119def ValType(val):
120 # type: (value_t) -> str
121 """Returns a user-facing string like Int, Eggex, BashArray, etc."""
122 return value_str(val.tag(), dot=False)
123
124
125def _FloatString(fl):
126 # type: (float) -> str
127
128 # Print in YSH syntax, similar to data_lang/j8.py
129 if math.isinf(fl):
130 s = 'INFINITY'
131 if fl < 0:
132 s = '-' + s
133 elif math.isnan(fl):
134 s = 'NAN'
135 else:
136 s = str(fl)
137 return s
138
139
140################
141# Measurements #
142################
143
144
145def TryUnicodeWidth(s):
146 # type: (str) -> int
147 try:
148 width = libc.wcswidth(s)
149 except UnicodeError:
150 # e.g. en_US.UTF-8 locale missing, just return the number of bytes
151 width = len(s)
152
153 if width == -1: # non-printable wide char
154 return len(s)
155
156 return width
157
158
159def _EmptyMeasure():
160 # type: () -> Measure
161 """The measure of an empty doc."""
162 return Measure(0, -1)
163
164
165def _FlattenMeasure(measure):
166 # type: (Measure) -> Measure
167 """The measure if its document is rendered flat."""
168 return Measure(measure.flat, -1)
169
170
171def _ConcatMeasure(m1, m2):
172 # type: (Measure, Measure) -> Measure
173 """Compute the measure of concatenated docs.
174
175 If m1 and m2 are the measures of doc1 and doc2,
176 then _ConcatMeasure(m1, m2) is the measure of doc.Concat([doc1, doc2]).
177 This concatenation is associative but not commutative."""
178 if m1.nonflat != -1:
179 return Measure(m1.flat + m2.flat, m1.nonflat)
180 elif m2.nonflat != -1:
181 return Measure(m1.flat + m2.flat, m1.flat + m2.nonflat)
182 else:
183 return Measure(m1.flat + m2.flat, -1)
184
185
186def _SuffixLen(measure):
187 # type: (Measure) -> int
188 """The width until the earliest possible newline, or end of document."""
189 if measure.nonflat != -1:
190 return measure.nonflat
191 else:
192 return measure.flat
193
194
195####################
196# Doc Construction #
197####################
198
199
200def _Text(string):
201 # type: (str) -> MeasuredDoc
202 """Print `string` (which must not contain a newline)."""
203 return MeasuredDoc(doc.Text(string), Measure(TryUnicodeWidth(string), -1))
204
205
206def _Break(string):
207 # type: (str) -> MeasuredDoc
208 """If in `flat` mode, print `string`, otherwise print `\n`."""
209 return MeasuredDoc(doc.Break(string), Measure(TryUnicodeWidth(string), 0))
210
211
212def _Indent(indent, mdoc):
213 # type: (int, MeasuredDoc) -> MeasuredDoc
214 """Add `indent` spaces after every newline in `mdoc`."""
215 return MeasuredDoc(doc.Indent(indent, mdoc), mdoc.measure)
216
217
218def _Concat(mdocs):
219 # type: (List[MeasuredDoc]) -> MeasuredDoc
220 """Print the mdocs in order (with no space in between)."""
221 measure = _EmptyMeasure()
222 for mdoc in mdocs:
223 measure = _ConcatMeasure(measure, mdoc.measure)
224 return MeasuredDoc(doc.Concat(mdocs), measure)
225
226
227def _Group(mdoc):
228 # type: (MeasuredDoc) -> MeasuredDoc
229 """Print `mdoc`. Use flat mode if `mdoc` will fit on the current line."""
230 return MeasuredDoc(doc.Group(mdoc), mdoc.measure)
231
232
233def _IfFlat(flat_mdoc, nonflat_mdoc):
234 # type: (MeasuredDoc, MeasuredDoc) -> MeasuredDoc
235 """If in flat mode, print `flat_mdoc` otherwise print `nonflat_mdoc`."""
236 return MeasuredDoc(
237 doc.IfFlat(flat_mdoc, nonflat_mdoc),
238 Measure(flat_mdoc.measure.flat, nonflat_mdoc.measure.nonflat))
239
240
241def _Flat(mdoc):
242 # type: (MeasuredDoc) -> MeasuredDoc
243 """Prints `mdoc` in flat mode."""
244 return MeasuredDoc(doc.Flat(mdoc), _FlattenMeasure(mdoc.measure))
245
246
247###################
248# Pretty Printing #
249###################
250
251
252class PrettyPrinter(object):
253
254 def __init__(self, max_width):
255 # type: (int) -> None
256 self.max_width = max_width
257
258 def _Fits(self, prefix_len, group, suffix_measure):
259 # type: (int, doc.Group, Measure) -> bool
260 """Will `group` fit flat on the current line?"""
261 measure = _ConcatMeasure(_FlattenMeasure(group.mdoc.measure),
262 suffix_measure)
263 return prefix_len + _SuffixLen(measure) <= self.max_width
264
265 def PrintDoc(self, document, buf):
266 # type: (MeasuredDoc, BufWriter) -> None
267 """Pretty print a `pretty.doc` to a BufWriter."""
268
269 # The width of the text we've printed so far on the current line
270 prefix_len = 0
271 # A _stack_ of document fragments to print. Each fragment contains:
272 # - A MeasuredDoc (doc node and its measure, saying how "big" it is)
273 # - The indentation level to print this doc node at.
274 # - Is this doc node being printed in flat mode?
275 # - The measure _from just after the doc node, to the end of the entire document_.
276 # (Call this the suffix_measure)
277 fragments = [DocFragment(_Group(document), 0, False, _EmptyMeasure())]
278
279 while len(fragments) > 0:
280 frag = fragments.pop()
281 with tagswitch(frag.mdoc.doc) as case:
282
283 if case(doc_e.Text):
284 text = cast(doc.Text, frag.mdoc.doc)
285 buf.write(text.string)
286 prefix_len += frag.mdoc.measure.flat
287
288 elif case(doc_e.Break):
289 if frag.is_flat:
290 break_str = cast(doc.Break, frag.mdoc.doc).string
291 buf.write(break_str)
292 prefix_len += frag.mdoc.measure.flat
293 else:
294 buf.write('\n')
295 buf.write_spaces(frag.indent)
296 prefix_len = frag.indent
297
298 elif case(doc_e.Indent):
299 indented = cast(doc.Indent, frag.mdoc.doc)
300 fragments.append(
301 DocFragment(indented.mdoc,
302 frag.indent + indented.indent,
303 frag.is_flat, frag.measure))
304
305 elif case(doc_e.Concat):
306 # If we encounter Concat([A, B, C]) with a suffix measure M,
307 # we need to push A,B,C onto the stack in reverse order:
308 # - C, with suffix_measure = B.measure + A.measure + M
309 # - B, with suffix_measure = A.measure + M
310 # - A, with suffix_measure = M
311 concat = cast(doc.Concat, frag.mdoc.doc)
312 measure = frag.measure
313 for mdoc in reversed(concat.mdocs):
314 fragments.append(
315 DocFragment(mdoc, frag.indent, frag.is_flat,
316 measure))
317 measure = _ConcatMeasure(mdoc.measure, measure)
318
319 elif case(doc_e.Group):
320 # If the group would fit on the current line when printed
321 # flat, do so. Otherwise, print it non-flat.
322 group = cast(doc.Group, frag.mdoc.doc)
323 flat = self._Fits(prefix_len, group, frag.measure)
324 fragments.append(
325 DocFragment(group.mdoc, frag.indent, flat,
326 frag.measure))
327
328 elif case(doc_e.IfFlat):
329 if_flat = cast(doc.IfFlat, frag.mdoc.doc)
330 if frag.is_flat:
331 subdoc = if_flat.flat_mdoc
332 else:
333 subdoc = if_flat.nonflat_mdoc
334 fragments.append(
335 DocFragment(subdoc, frag.indent, frag.is_flat,
336 frag.measure))
337
338 elif case(doc_e.Flat):
339 flat_doc = cast(doc.Flat, frag.mdoc.doc)
340 fragments.append(
341 DocFragment(flat_doc.mdoc, frag.indent, True,
342 frag.measure))
343
344
345################
346# Value -> Doc #
347################
348
349
350class ValueEncoder:
351 """Converts Oils values into `doc`s, which can then be pretty printed."""
352
353 def __init__(self):
354 # type: () -> None
355
356 # Default values
357 self.indent = 4
358 self.use_styles = True
359 # Tuned for 'data_lang/pretty-benchmark.sh float-demo'
360 # TODO: might want options for float width
361 self.max_tabular_width = 22
362
363 self.ysh_style = True
364
365 self.visiting = {} # type: Dict[int, bool]
366
367 # These can be configurable later
368 self.int_style = ansi.YELLOW
369 self.float_style = ansi.BLUE
370 self.null_style = ansi.RED
371 self.bool_style = ansi.CYAN
372 self.string_style = ansi.GREEN
373 self.cycle_style = ansi.BOLD + ansi.BLUE
374 self.type_style = ansi.MAGENTA
375
376 def SetIndent(self, indent):
377 # type: (int) -> None
378 """Set the number of spaces per indent."""
379 self.indent = indent
380
381 def SetUseStyles(self, use_styles):
382 # type: (bool) -> None
383 """Print with ansi colors and styles, rather than plain text."""
384 self.use_styles = use_styles
385
386 def SetMaxTabularWidth(self, max_tabular_width):
387 # type: (int) -> None
388 """Set the maximum width that list elements can be, for them to be
389 vertically aligned."""
390 self.max_tabular_width = max_tabular_width
391
392 def TypePrefix(self, type_str):
393 # type: (str) -> List[MeasuredDoc]
394 """Return docs for type string "(List)", which may break afterward."""
395 type_name = self._Styled(self.type_style, _Text(type_str))
396
397 n = len(type_str)
398 # Our maximum string is "Float"
399 assert n <= 5, type_str
400
401 # Start printing in column 8. Adjust to 6 because () takes 2 spaces.
402 spaces = ' ' * (6 - n)
403
404 mdocs = [_Text("("), type_name, _Text(")"), _Break(spaces)]
405 return mdocs
406
407 def Value(self, val):
408 # type: (value_t) -> MeasuredDoc
409 """Convert an Oils value into a `doc`, which can then be pretty printed."""
410 self.visiting.clear()
411 return self._Value(val)
412
413 def _Styled(self, style, mdoc):
414 # type: (str, MeasuredDoc) -> MeasuredDoc
415 """Apply the ANSI style string to the given node, if use_styles is set."""
416 if self.use_styles:
417 return _Concat([
418 MeasuredDoc(doc.Text(style), _EmptyMeasure()), mdoc,
419 MeasuredDoc(doc.Text(ansi.RESET), _EmptyMeasure())
420 ])
421 else:
422 return mdoc
423
424 def _Surrounded(self, open, mdoc, close):
425 # type: (str, MeasuredDoc, str) -> MeasuredDoc
426 """Print one of two options (using '[', ']' for open, close):
427
428 ```
429 [mdoc]
430 ------
431 [
432 mdoc
433 ]
434 ```
435 """
436 return _Group(
437 _Concat([
438 _Text(open),
439 _Indent(self.indent, _Concat([_Break(""), mdoc])),
440 _Break(""),
441 _Text(close)
442 ]))
443
444 def _SurroundedAndPrefixed(self, open, prefix, sep, mdoc, close):
445 # type: (str, MeasuredDoc, str, MeasuredDoc, str) -> MeasuredDoc
446 """Print one of two options
447 (using '[', 'prefix', ':', 'mdoc', ']' for open, prefix, sep, mdoc, close):
448
449 ```
450 [prefix:mdoc]
451 ------
452 [prefix
453 mdoc
454 ]
455 ```
456 """
457 return _Group(
458 _Concat([
459 _Text(open), prefix,
460 _Indent(self.indent, _Concat([_Break(sep), mdoc])),
461 _Break(""),
462 _Text(close)
463 ]))
464
465 def _Join(self, items, sep, space):
466 # type: (List[MeasuredDoc], str, str) -> MeasuredDoc
467 """Join `items`, using either 'sep+space' or 'sep+newline' between them.
468
469 E.g., if sep and space are ',' and '_', print one of these two cases:
470 ```
471 first,_second,_third
472 ------
473 first,
474 second,
475 third
476 ```
477 """
478 seq = [] # type: List[MeasuredDoc]
479 for i, item in enumerate(items):
480 if i != 0:
481 seq.append(_Text(sep))
482 seq.append(_Break(space))
483 seq.append(item)
484 return _Concat(seq)
485
486 def _Tabular(self, items, sep):
487 # type: (List[MeasuredDoc], str) -> MeasuredDoc
488 """Join `items` together, using one of three styles:
489
490 (showing spaces as underscores for clarity)
491 ```
492 first,_second,_third,_fourth,_fifth,_sixth,_seventh,_eighth
493 ------
494 first,___second,__third,
495 fourth,__fifth,___sixth,
496 seventh,_eighth
497 ------
498 first,
499 second,
500 third,
501 fourth,
502 fifth,
503 sixth,
504 seventh,
505 eighth
506 ```
507
508 The first "single line" style is used if the items fit on one line. The
509 second "tabular' style is used if the flat width of all items is no
510 greater than `self.max_tabular_width`. The third "multi line" style is
511 used otherwise.
512 """
513
514 # Why not "just" use tabular alignment so long as two items fit on every
515 # line? Because it isn't possible to check for that in the pretty
516 # printing language. There are two sorts of conditionals we can do:
517 #
518 # A. Inside the pretty printing language, which supports exactly one
519 # conditional: "does it fit on one line?".
520 # B. Outside the pretty printing language we can run arbitrary Python
521 # code, but we don't know how much space is available on the line
522 # because it depends on the context in which we're printed, which may
523 # vary.
524 #
525 # We're picking between the three styles, by using (A) to check if the
526 # first style fits on one line, then using (B) with "are all the items
527 # smaller than `self.max_tabular_width`?" to pick between style 2 and
528 # style 3.
529
530 if len(items) == 0:
531 return _Text("")
532
533 max_flat_len = 0
534 seq = [] # type: List[MeasuredDoc]
535 for i, item in enumerate(items):
536 if i != 0:
537 seq.append(_Text(sep))
538 seq.append(_Break(" "))
539 seq.append(item)
540 max_flat_len = max(max_flat_len, item.measure.flat)
541 non_tabular = _Concat(seq)
542
543 sep_width = TryUnicodeWidth(sep)
544 if max_flat_len + sep_width + 1 <= self.max_tabular_width:
545 tabular_seq = [] # type: List[MeasuredDoc]
546 for i, item in enumerate(items):
547 tabular_seq.append(_Flat(item))
548 if i != len(items) - 1:
549 padding = max_flat_len - item.measure.flat + 1
550 tabular_seq.append(_Text(sep))
551 tabular_seq.append(_Group(_Break(" " * padding)))
552 tabular = _Concat(tabular_seq)
553 return _Group(_IfFlat(non_tabular, tabular))
554 else:
555 return non_tabular
556
557 def _DictKey(self, s):
558 # type: (str) -> MeasuredDoc
559 if match.IsValidVarName(s):
560 encoded = s
561 else:
562 if self.ysh_style:
563 encoded = j8_lite.YshEncodeString(s)
564 else:
565 # TODO: remove this dead branch after fixing tests
566 encoded = j8_lite.EncodeString(s)
567 return _Text(encoded)
568
569 def _StringLiteral(self, s):
570 # type: (str) -> MeasuredDoc
571 if self.ysh_style:
572 # YSH r'' or b'' style
573 encoded = j8_lite.YshEncodeString(s)
574 else:
575 # TODO: remove this dead branch after fixing tests
576 encoded = j8_lite.EncodeString(s)
577 return self._Styled(self.string_style, _Text(encoded))
578
579 def _BashStringLiteral(self, s):
580 # type: (str) -> MeasuredDoc
581
582 # '' or $'' style
583 #
584 # We mimic bash syntax by using $'\\' instead of b'\\'
585 #
586 # $ declare -a array=($'\\')
587 # $ = array
588 # (BashArray) (BashArray $'\\')
589 #
590 # $ declare -A assoc=([k]=$'\\')
591 # $ = assoc
592 # (BashAssoc) (BashAssoc ['k']=$'\\')
593
594 encoded = j8_lite.ShellEncode(s)
595 return self._Styled(self.string_style, _Text(encoded))
596
597 def _YshList(self, vlist):
598 # type: (value.List) -> MeasuredDoc
599 """Print a string literal."""
600 if len(vlist.items) == 0:
601 return _Text("[]")
602 mdocs = [self._Value(item) for item in vlist.items]
603 return self._Surrounded("[", self._Tabular(mdocs, ","), "]")
604
605 def _YshDict(self, vdict):
606 # type: (value.Dict) -> MeasuredDoc
607 if len(vdict.d) == 0:
608 return _Text("{}")
609 mdocs = [] # type: List[MeasuredDoc]
610 for k, v in iteritems(vdict.d):
611 mdocs.append(
612 _Concat([self._DictKey(k),
613 _Text(": "),
614 self._Value(v)]))
615 return self._Surrounded("{", self._Join(mdocs, ",", " "), "}")
616
617 def _BashArray(self, varray):
618 # type: (value.BashArray) -> MeasuredDoc
619 type_name = self._Styled(self.type_style, _Text("BashArray"))
620 if len(varray.strs) == 0:
621 return _Concat([_Text("("), type_name, _Text(")")])
622 mdocs = [] # type: List[MeasuredDoc]
623 for s in varray.strs:
624 if s is None:
625 mdocs.append(_Text("null"))
626 else:
627 mdocs.append(self._BashStringLiteral(s))
628 return self._SurroundedAndPrefixed("(", type_name, " ",
629 self._Tabular(mdocs, ""), ")")
630
631 def _BashAssoc(self, vassoc):
632 # type: (value.BashAssoc) -> MeasuredDoc
633 type_name = self._Styled(self.type_style, _Text("BashAssoc"))
634 if len(vassoc.d) == 0:
635 return _Concat([_Text("("), type_name, _Text(")")])
636 mdocs = [] # type: List[MeasuredDoc]
637 for k2, v2 in iteritems(vassoc.d):
638 mdocs.append(
639 _Concat([
640 _Text("["),
641 self._BashStringLiteral(k2),
642 _Text("]="),
643 self._BashStringLiteral(v2)
644 ]))
645 return self._SurroundedAndPrefixed("(", type_name, " ",
646 self._Join(mdocs, "", " "), ")")
647
648 def _SparseArray(self, val):
649 # type: (value.SparseArray) -> MeasuredDoc
650 type_name = self._Styled(self.type_style, _Text("SparseArray"))
651 if len(val.d) == 0:
652 return _Concat([_Text("("), type_name, _Text(")")])
653 mdocs = [] # type: List[MeasuredDoc]
654 for k2, v2 in iteritems(val.d):
655 mdocs.append(
656 _Concat([
657 _Text("["),
658 self._Styled(self.int_style, _Text(mops.ToStr(k2))),
659 _Text("]="),
660 self._BashStringLiteral(v2)
661 ]))
662 return self._SurroundedAndPrefixed("(", type_name, " ",
663 self._Join(mdocs, "", " "), ")")
664
665 def _Value(self, val):
666 # type: (value_t) -> MeasuredDoc
667
668 with tagswitch(val) as case:
669 if case(value_e.Null):
670 return self._Styled(self.null_style, _Text("null"))
671
672 elif case(value_e.Bool):
673 b = cast(value.Bool, val).b
674 return self._Styled(self.bool_style,
675 _Text("true" if b else "false"))
676
677 elif case(value_e.Int):
678 i = cast(value.Int, val).i
679 return self._Styled(self.int_style, _Text(mops.ToStr(i)))
680
681 elif case(value_e.Float):
682 f = cast(value.Float, val).f
683 return self._Styled(self.float_style, _Text(_FloatString(f)))
684
685 elif case(value_e.Str):
686 s = cast(value.Str, val).s
687 return self._StringLiteral(s)
688
689 elif case(value_e.Range):
690 r = cast(value.Range, val)
691 type_name = self._Styled(self.type_style, _Text(ValType(r)))
692 mdocs = [_Text(str(r.lower)), _Text(".."), _Text(str(r.upper))]
693 return self._SurroundedAndPrefixed("(", type_name, " ",
694 self._Join(mdocs, "", " "),
695 ")")
696
697 elif case(value_e.List):
698 vlist = cast(value.List, val)
699 heap_id = j8.HeapValueId(vlist)
700 if self.visiting.get(heap_id, False):
701 return _Concat([
702 _Text("["),
703 self._Styled(self.cycle_style, _Text("...")),
704 _Text("]")
705 ])
706 else:
707 self.visiting[heap_id] = True
708 result = self._YshList(vlist)
709 self.visiting[heap_id] = False
710 return result
711
712 elif case(value_e.Dict):
713 vdict = cast(value.Dict, val)
714 heap_id = j8.HeapValueId(vdict)
715 if self.visiting.get(heap_id, False):
716 return _Concat([
717 _Text("{"),
718 self._Styled(self.cycle_style, _Text("...")),
719 _Text("}")
720 ])
721 else:
722 self.visiting[heap_id] = True
723 result = self._YshDict(vdict)
724 self.visiting[heap_id] = False
725 return result
726
727 elif case(value_e.SparseArray):
728 sparse = cast(value.SparseArray, val)
729 return self._SparseArray(sparse)
730
731 elif case(value_e.BashArray):
732 varray = cast(value.BashArray, val)
733 return self._BashArray(varray)
734
735 elif case(value_e.BashAssoc):
736 vassoc = cast(value.BashAssoc, val)
737 return self._BashAssoc(vassoc)
738
739 else:
740 type_name = self._Styled(self.type_style, _Text(ValType(val)))
741 id_str = j8.ValueIdString(val)
742 return _Concat([_Text("<"), type_name, _Text(id_str + ">")])
743
744
745# vim: sw=4