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

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