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

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