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

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