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
435 self.bool_style = ansi.YELLOW
436 self.string_style = ansi.GREEN
437 self.cycle_style = ansi.BOLD + ansi.BLUE
438 self.type_style = ansi.MAGENTA
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 type_name = self._Styled(self.type_style, _Text(ValType(val)))
449 mdocs = [_Text("("), type_name, _Text(")"), _Break(" ")]
450 else:
451 mdocs = []
452
453 mdocs.append(self._Value(val))
454 return _Group(_Concat(mdocs))
455 else:
456 return self._Value(val)
457
458 def _Styled(self, style, mdoc):
459 # type: (str, MeasuredDoc) -> MeasuredDoc
460 """Apply the ANSI style string to the given node, if use_styles is set."""
461 if self.use_styles:
462 return _Concat([
463 MeasuredDoc(doc.Text(style), _EmptyMeasure()), mdoc,
464 MeasuredDoc(doc.Text(ansi.RESET), _EmptyMeasure())
465 ])
466 else:
467 return mdoc
468
469 def _Surrounded(self, open, mdoc, close):
470 # type: (str, MeasuredDoc, str) -> MeasuredDoc
471 """Print one of two options (using '[', ']' for open, close):
472
473 ```
474 [mdoc]
475 ------
476 [
477 mdoc
478 ]
479 ```
480 """
481 return _Group(
482 _Concat([
483 _Text(open),
484 _Indent(self.indent, _Concat([_Break(""), mdoc])),
485 _Break(""),
486 _Text(close)
487 ]))
488
489 def _SurroundedAndPrefixed(self, open, prefix, sep, mdoc, close):
490 # type: (str, MeasuredDoc, str, MeasuredDoc, str) -> MeasuredDoc
491 """Print one of two options
492 (using '[', 'prefix', ':', 'mdoc', ']' for open, prefix, sep, mdoc, close):
493
494 ```
495 [prefix:mdoc]
496 ------
497 [prefix
498 mdoc
499 ]
500 ```
501 """
502 return _Group(
503 _Concat([
504 _Text(open), prefix,
505 _Indent(self.indent, _Concat([_Break(sep), mdoc])),
506 _Break(""),
507 _Text(close)
508 ]))
509
510 def _Join(self, items, sep, space):
511 # type: (List[MeasuredDoc], str, str) -> MeasuredDoc
512 """Join `items`, using either 'sep+space' or 'sep+newline' between them.
513
514 E.g., if sep and space are ',' and '_', print one of these two cases:
515 ```
516 first,_second,_third
517 ------
518 first,
519 second,
520 third
521 ```
522 """
523 seq = [] # type: List[MeasuredDoc]
524 for i, item in enumerate(items):
525 if i != 0:
526 seq.append(_Text(sep))
527 seq.append(_Break(space))
528 seq.append(item)
529 return _Concat(seq)
530
531 def _Tabular(self, items, sep):
532 # type: (List[MeasuredDoc], str) -> MeasuredDoc
533 """Join `items` together, using one of three styles:
534
535 (showing spaces as underscores for clarity)
536 ```
537 first,_second,_third,_fourth,_fifth,_sixth,_seventh,_eighth
538 ------
539 first,___second,__third,
540 fourth,__fifth,___sixth,
541 seventh,_eighth
542 ------
543 first,
544 second,
545 third,
546 fourth,
547 fifth,
548 sixth,
549 seventh,
550 eighth
551 ```
552
553 The first "single line" style is used if the items fit on one line. The
554 second "tabular' style is used if the flat width of all items is no
555 greater than `self.max_tabular_width`. The third "multi line" style is
556 used otherwise.
557 """
558
559 # Why not "just" use tabular alignment so long as two items fit on every
560 # line? Because it isn't possible to check for that in the pretty
561 # printing language. There are two sorts of conditionals we can do:
562 #
563 # A. Inside the pretty printing language, which supports exactly one
564 # conditional: "does it fit on one line?".
565 # B. Outside the pretty printing language we can run arbitrary Python
566 # code, but we don't know how much space is available on the line
567 # because it depends on the context in which we're printed, which may
568 # vary.
569 #
570 # We're picking between the three styles, by using (A) to check if the
571 # first style fits on one line, then using (B) with "are all the items
572 # smaller than `self.max_tabular_width`?" to pick between style 2 and
573 # style 3.
574
575 if len(items) == 0:
576 return _Text("")
577
578 max_flat_len = 0
579 seq = [] # type: List[MeasuredDoc]
580 for i, item in enumerate(items):
581 if i != 0:
582 seq.append(_Text(sep))
583 seq.append(_Break(" "))
584 seq.append(item)
585 max_flat_len = max(max_flat_len, item.measure.flat)
586 non_tabular = _Concat(seq)
587
588 sep_width = TryUnicodeWidth(sep)
589 if max_flat_len + sep_width + 1 <= self.max_tabular_width:
590 tabular_seq = [] # type: List[MeasuredDoc]
591 for i, item in enumerate(items):
592 tabular_seq.append(_Flat(item))
593 if i != len(items) - 1:
594 padding = max_flat_len - item.measure.flat + 1
595 tabular_seq.append(_Text(sep))
596 tabular_seq.append(_Group(_Break(" " * padding)))
597 tabular = _Concat(tabular_seq)
598 return _Group(_IfFlat(non_tabular, tabular))
599 else:
600 return non_tabular
601
602 def _DictKey(self, s):
603 # type: (str) -> MeasuredDoc
604 if match.IsValidVarName(s):
605 encoded = s
606 else:
607 if self.ysh_style:
608 encoded = j8_lite.YshEncodeString(s)
609 else:
610 # TODO: remove this dead branch after fixing tests
611 encoded = j8_lite.EncodeString(s)
612 return _Text(encoded)
613
614 def _StringLiteral(self, s):
615 # type: (str) -> MeasuredDoc
616 if self.ysh_style:
617 # YSH r'' or b'' style
618 encoded = j8_lite.YshEncodeString(s)
619 else:
620 # TODO: remove this dead branch after fixing tests
621 encoded = j8_lite.EncodeString(s)
622 return self._Styled(self.string_style, _Text(encoded))
623
624 def _BashStringLiteral(self, s):
625 # type: (str) -> MeasuredDoc
626
627 # '' or $'' style
628 #
629 # We mimic bash syntax by using $'\\' instead of b'\\'
630 #
631 # $ declare -a array=($'\\')
632 # $ = array
633 # (BashArray) (BashArray $'\\')
634 #
635 # $ declare -A assoc=([k]=$'\\')
636 # $ = assoc
637 # (BashAssoc) (BashAssoc ['k']=$'\\')
638
639 encoded = j8_lite.ShellEncode(s)
640 return self._Styled(self.string_style, _Text(encoded))
641
642 def _YshList(self, vlist):
643 # type: (value.List) -> MeasuredDoc
644 """Print a string literal."""
645 if len(vlist.items) == 0:
646 return _Text("[]")
647 mdocs = [self._Value(item) for item in vlist.items]
648 return self._Surrounded("[", self._Tabular(mdocs, ","), "]")
649
650 def _YshDict(self, vdict):
651 # type: (value.Dict) -> MeasuredDoc
652 if len(vdict.d) == 0:
653 return _Text("{}")
654 mdocs = [] # type: List[MeasuredDoc]
655 for k, v in iteritems(vdict.d):
656 mdocs.append(
657 _Concat([self._DictKey(k),
658 _Text(": "),
659 self._Value(v)]))
660 return self._Surrounded("{", self._Join(mdocs, ",", " "), "}")
661
662 def _BashArray(self, varray):
663 # type: (value.BashArray) -> MeasuredDoc
664 type_name = self._Styled(self.type_style, _Text("BashArray"))
665 if len(varray.strs) == 0:
666 return _Concat([_Text("("), type_name, _Text(")")])
667 mdocs = [] # type: List[MeasuredDoc]
668 for s in varray.strs:
669 if s is None:
670 mdocs.append(_Text("null"))
671 else:
672 mdocs.append(self._BashStringLiteral(s))
673 return self._SurroundedAndPrefixed("(", type_name, " ",
674 self._Tabular(mdocs, ""), ")")
675
676 def _BashAssoc(self, vassoc):
677 # type: (value.BashAssoc) -> MeasuredDoc
678 type_name = self._Styled(self.type_style, _Text("BashAssoc"))
679 if len(vassoc.d) == 0:
680 return _Concat([_Text("("), type_name, _Text(")")])
681 mdocs = [] # type: List[MeasuredDoc]
682 for k2, v2 in iteritems(vassoc.d):
683 mdocs.append(
684 _Concat([
685 _Text("["),
686 self._BashStringLiteral(k2),
687 _Text("]="),
688 self._BashStringLiteral(v2)
689 ]))
690 return self._SurroundedAndPrefixed("(", type_name, " ",
691 self._Join(mdocs, "", " "), ")")
692
693 def _Value(self, val):
694 # type: (value_t) -> MeasuredDoc
695
696 with tagswitch(val) as case:
697 if case(value_e.Null):
698 return self._Styled(self.null_style, _Text("null"))
699
700 elif case(value_e.Bool):
701 b = cast(value.Bool, val).b
702 return self._Styled(self.bool_style,
703 _Text("true" if b else "false"))
704
705 elif case(value_e.Int):
706 i = cast(value.Int, val).i
707 return self._Styled(self.number_style, _Text(mops.ToStr(i)))
708
709 elif case(value_e.Float):
710 f = cast(value.Float, val).f
711 return self._Styled(self.number_style, _Text(_FloatString(f)))
712
713 elif case(value_e.Str):
714 s = cast(value.Str, val).s
715 return self._StringLiteral(s)
716
717 elif case(value_e.Range):
718 r = cast(value.Range, val)
719 type_name = self._Styled(self.type_style, _Text(ValType(r)))
720 mdocs = [_Text(str(r.lower)), _Text(".."), _Text(str(r.upper))]
721 return self._SurroundedAndPrefixed("(", type_name, " ",
722 self._Join(mdocs, "", " "),
723 ")")
724
725 elif case(value_e.List):
726 vlist = cast(value.List, val)
727 heap_id = j8.HeapValueId(vlist)
728 if self.visiting.get(heap_id, False):
729 return _Concat([
730 _Text("["),
731 self._Styled(self.cycle_style, _Text("...")),
732 _Text("]")
733 ])
734 else:
735 self.visiting[heap_id] = True
736 result = self._YshList(vlist)
737 self.visiting[heap_id] = False
738 return result
739
740 elif case(value_e.Dict):
741 vdict = cast(value.Dict, val)
742 heap_id = j8.HeapValueId(vdict)
743 if self.visiting.get(heap_id, False):
744 return _Concat([
745 _Text("{"),
746 self._Styled(self.cycle_style, _Text("...")),
747 _Text("}")
748 ])
749 else:
750 self.visiting[heap_id] = True
751 result = self._YshDict(vdict)
752 self.visiting[heap_id] = False
753 return result
754
755 elif case(value_e.BashArray):
756 varray = cast(value.BashArray, val)
757 return self._BashArray(varray)
758
759 elif case(value_e.BashAssoc):
760 vassoc = cast(value.BashAssoc, val)
761 return self._BashAssoc(vassoc)
762
763 else:
764 type_name = self._Styled(self.type_style, _Text(ValType(val)))
765 id_str = j8.ValueIdString(val)
766 return _Concat([_Text("<"), type_name, _Text(id_str + ">")])
767
768
769# vim: sw=4