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

777 lines, 373 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
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
269 def SetMaxWidth(self, max_width):
270 # type: (int) -> None
271 """Set the maximum line width.
272
273 Pretty printing will attempt to (but does not guarantee to) fit the doc
274 within this width.
275 """
276 self.max_width = max_width
277
278 def _Fits(self, prefix_len, group, suffix_measure):
279 # type: (int, doc.Group, Measure) -> bool
280 """Will `group` fit flat on the current line?"""
281 measure = _ConcatMeasure(_FlattenMeasure(group.mdoc.measure),
282 suffix_measure)
283 return prefix_len + _SuffixLen(measure) <= self.max_width
284
285 def PrintDoc(self, document, buf):
286 # type: (MeasuredDoc, BufWriter) -> None
287 """Pretty print a `pretty.doc` to a BufWriter."""
288
289 # The width of the text we've printed so far on the current line
290 prefix_len = 0
291 # A _stack_ of document fragments to print. Each fragment contains:
292 # - A MeasuredDoc (doc node and its measure, saying how "big" it is)
293 # - The indentation level to print this doc node at.
294 # - Is this doc node being printed in flat mode?
295 # - The measure _from just after the doc node, to the end of the entire document_.
296 # (Call this the suffix_measure)
297 fragments = [DocFragment(_Group(document), 0, False, _EmptyMeasure())]
298
299 while len(fragments) > 0:
300 frag = fragments.pop()
301 with tagswitch(frag.mdoc.doc) as case:
302
303 if case(doc_e.Text):
304 text = cast(doc.Text, frag.mdoc.doc)
305 buf.write(text.string)
306 prefix_len += frag.mdoc.measure.flat
307
308 elif case(doc_e.Break):
309 if frag.is_flat:
310 break_str = cast(doc.Break, frag.mdoc.doc).string
311 buf.write(break_str)
312 prefix_len += frag.mdoc.measure.flat
313 else:
314 buf.write('\n')
315 buf.write_spaces(frag.indent)
316 prefix_len = frag.indent
317
318 elif case(doc_e.Indent):
319 indented = cast(doc.Indent, frag.mdoc.doc)
320 fragments.append(
321 DocFragment(indented.mdoc,
322 frag.indent + indented.indent,
323 frag.is_flat, frag.measure))
324
325 elif case(doc_e.Concat):
326 # If we encounter Concat([A, B, C]) with a suffix measure M,
327 # we need to push A,B,C onto the stack in reverse order:
328 # - C, with suffix_measure = B.measure + A.measure + M
329 # - B, with suffix_measure = A.measure + M
330 # - A, with suffix_measure = M
331 concat = cast(doc.Concat, frag.mdoc.doc)
332 measure = frag.measure
333 for mdoc in reversed(concat.mdocs):
334 fragments.append(
335 DocFragment(mdoc, frag.indent, frag.is_flat,
336 measure))
337 measure = _ConcatMeasure(mdoc.measure, measure)
338
339 elif case(doc_e.Group):
340 # If the group would fit on the current line when printed
341 # flat, do so. Otherwise, print it non-flat.
342 group = cast(doc.Group, frag.mdoc.doc)
343 flat = self._Fits(prefix_len, group, frag.measure)
344 fragments.append(
345 DocFragment(group.mdoc, frag.indent, flat,
346 frag.measure))
347
348 elif case(doc_e.IfFlat):
349 if_flat = cast(doc.IfFlat, frag.mdoc.doc)
350 if frag.is_flat:
351 subdoc = if_flat.flat_mdoc
352 else:
353 subdoc = if_flat.nonflat_mdoc
354 fragments.append(
355 DocFragment(subdoc, frag.indent, frag.is_flat,
356 frag.measure))
357
358 elif case(doc_e.Flat):
359 flat_doc = cast(doc.Flat, frag.mdoc.doc)
360 fragments.append(
361 DocFragment(flat_doc.mdoc, frag.indent, True,
362 frag.measure))
363
364
365################
366# Value -> Doc #
367################
368
369_DEFAULT_INDENTATION = 4
370_DEFAULT_USE_STYLES = True
371_DEFAULT_SHOW_TYPE_PREFIX = True
372
373# Tuned for 'data_lang/pretty-benchmark.sh float-demo'
374# TODO: might want options for float width
375_DEFAULT_MAX_TABULAR_WIDTH = 22
376
377
378class ValueEncoder:
379 """Converts Oils values into `doc`s, which can then be pretty printed."""
380
381 def __init__(self):
382 # type: () -> None
383 self.indent = _DEFAULT_INDENTATION
384 self.use_styles = _DEFAULT_USE_STYLES
385 self.show_type_prefix = _DEFAULT_SHOW_TYPE_PREFIX
386 self.max_tabular_width = _DEFAULT_MAX_TABULAR_WIDTH
387 self.ysh_style = False
388
389 self.visiting = {} # type: Dict[int, bool]
390
391 # These can be configurable later
392 self.int_style = ansi.YELLOW
393 self.float_style = ansi.BLUE
394 self.null_style = ansi.RED
395 self.bool_style = ansi.CYAN
396 self.string_style = ansi.GREEN
397 self.cycle_style = ansi.BOLD + ansi.BLUE
398 self.type_style = ansi.MAGENTA
399
400 def SetIndent(self, indent):
401 # type: (int) -> None
402 """Set the number of spaces per indent."""
403 self.indent = indent
404
405 def SetUseStyles(self, use_styles):
406 # type: (bool) -> None
407 """Print with ansi colors and styles, rather than plain text."""
408 self.use_styles = use_styles
409
410 def SetShowTypePrefix(self, show_type_prefix):
411 # type: (bool) -> None
412 """Set whether or not to print a type before the top-level value.
413
414 E.g. `(Bool) true`"""
415 self.show_type_prefix = show_type_prefix
416
417 def SetMaxTabularWidth(self, max_tabular_width):
418 # type: (int) -> None
419 """Set the maximum width that list elements can be, for them to be
420 vertically aligned."""
421 self.max_tabular_width = max_tabular_width
422
423 def SetYshStyle(self):
424 # type: () -> None
425 self.ysh_style = True
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 # These JSON-like types have a special notation, so print type
433 # explicitly
434 if TypeNotPrinted(val):
435 type_name = self._Styled(self.type_style, _Text(ValType(val)))
436 mdocs = [_Text("("), type_name, _Text(")"), _Break(" ")]
437 else:
438 mdocs = []
439
440 mdocs.append(self._Value(val))
441 return _Group(_Concat(mdocs))
442 else:
443 return self._Value(val)
444
445 def _Styled(self, style, mdoc):
446 # type: (str, MeasuredDoc) -> MeasuredDoc
447 """Apply the ANSI style string to the given node, if use_styles is set."""
448 if self.use_styles:
449 return _Concat([
450 MeasuredDoc(doc.Text(style), _EmptyMeasure()), mdoc,
451 MeasuredDoc(doc.Text(ansi.RESET), _EmptyMeasure())
452 ])
453 else:
454 return mdoc
455
456 def _Surrounded(self, open, mdoc, close):
457 # type: (str, MeasuredDoc, str) -> MeasuredDoc
458 """Print one of two options (using '[', ']' for open, close):
459
460 ```
461 [mdoc]
462 ------
463 [
464 mdoc
465 ]
466 ```
467 """
468 return _Group(
469 _Concat([
470 _Text(open),
471 _Indent(self.indent, _Concat([_Break(""), mdoc])),
472 _Break(""),
473 _Text(close)
474 ]))
475
476 def _SurroundedAndPrefixed(self, open, prefix, sep, mdoc, close):
477 # type: (str, MeasuredDoc, str, MeasuredDoc, str) -> MeasuredDoc
478 """Print one of two options
479 (using '[', 'prefix', ':', 'mdoc', ']' for open, prefix, sep, mdoc, close):
480
481 ```
482 [prefix:mdoc]
483 ------
484 [prefix
485 mdoc
486 ]
487 ```
488 """
489 return _Group(
490 _Concat([
491 _Text(open), prefix,
492 _Indent(self.indent, _Concat([_Break(sep), mdoc])),
493 _Break(""),
494 _Text(close)
495 ]))
496
497 def _Join(self, items, sep, space):
498 # type: (List[MeasuredDoc], str, str) -> MeasuredDoc
499 """Join `items`, using either 'sep+space' or 'sep+newline' between them.
500
501 E.g., if sep and space are ',' and '_', print one of these two cases:
502 ```
503 first,_second,_third
504 ------
505 first,
506 second,
507 third
508 ```
509 """
510 seq = [] # type: List[MeasuredDoc]
511 for i, item in enumerate(items):
512 if i != 0:
513 seq.append(_Text(sep))
514 seq.append(_Break(space))
515 seq.append(item)
516 return _Concat(seq)
517
518 def _Tabular(self, items, sep):
519 # type: (List[MeasuredDoc], str) -> MeasuredDoc
520 """Join `items` together, using one of three styles:
521
522 (showing spaces as underscores for clarity)
523 ```
524 first,_second,_third,_fourth,_fifth,_sixth,_seventh,_eighth
525 ------
526 first,___second,__third,
527 fourth,__fifth,___sixth,
528 seventh,_eighth
529 ------
530 first,
531 second,
532 third,
533 fourth,
534 fifth,
535 sixth,
536 seventh,
537 eighth
538 ```
539
540 The first "single line" style is used if the items fit on one line. The
541 second "tabular' style is used if the flat width of all items is no
542 greater than `self.max_tabular_width`. The third "multi line" style is
543 used otherwise.
544 """
545
546 # Why not "just" use tabular alignment so long as two items fit on every
547 # line? Because it isn't possible to check for that in the pretty
548 # printing language. There are two sorts of conditionals we can do:
549 #
550 # A. Inside the pretty printing language, which supports exactly one
551 # conditional: "does it fit on one line?".
552 # B. Outside the pretty printing language we can run arbitrary Python
553 # code, but we don't know how much space is available on the line
554 # because it depends on the context in which we're printed, which may
555 # vary.
556 #
557 # We're picking between the three styles, by using (A) to check if the
558 # first style fits on one line, then using (B) with "are all the items
559 # smaller than `self.max_tabular_width`?" to pick between style 2 and
560 # style 3.
561
562 if len(items) == 0:
563 return _Text("")
564
565 max_flat_len = 0
566 seq = [] # type: List[MeasuredDoc]
567 for i, item in enumerate(items):
568 if i != 0:
569 seq.append(_Text(sep))
570 seq.append(_Break(" "))
571 seq.append(item)
572 max_flat_len = max(max_flat_len, item.measure.flat)
573 non_tabular = _Concat(seq)
574
575 sep_width = TryUnicodeWidth(sep)
576 if max_flat_len + sep_width + 1 <= self.max_tabular_width:
577 tabular_seq = [] # type: List[MeasuredDoc]
578 for i, item in enumerate(items):
579 tabular_seq.append(_Flat(item))
580 if i != len(items) - 1:
581 padding = max_flat_len - item.measure.flat + 1
582 tabular_seq.append(_Text(sep))
583 tabular_seq.append(_Group(_Break(" " * padding)))
584 tabular = _Concat(tabular_seq)
585 return _Group(_IfFlat(non_tabular, tabular))
586 else:
587 return non_tabular
588
589 def _DictKey(self, s):
590 # type: (str) -> MeasuredDoc
591 if match.IsValidVarName(s):
592 encoded = s
593 else:
594 if self.ysh_style:
595 encoded = j8_lite.YshEncodeString(s)
596 else:
597 # TODO: remove this dead branch after fixing tests
598 encoded = j8_lite.EncodeString(s)
599 return _Text(encoded)
600
601 def _StringLiteral(self, s):
602 # type: (str) -> MeasuredDoc
603 if self.ysh_style:
604 # YSH r'' or b'' style
605 encoded = j8_lite.YshEncodeString(s)
606 else:
607 # TODO: remove this dead branch after fixing tests
608 encoded = j8_lite.EncodeString(s)
609 return self._Styled(self.string_style, _Text(encoded))
610
611 def _BashStringLiteral(self, s):
612 # type: (str) -> MeasuredDoc
613
614 # '' or $'' style
615 #
616 # We mimic bash syntax by using $'\\' instead of b'\\'
617 #
618 # $ declare -a array=($'\\')
619 # $ = array
620 # (BashArray) (BashArray $'\\')
621 #
622 # $ declare -A assoc=([k]=$'\\')
623 # $ = assoc
624 # (BashAssoc) (BashAssoc ['k']=$'\\')
625
626 encoded = j8_lite.ShellEncode(s)
627 return self._Styled(self.string_style, _Text(encoded))
628
629 def _YshList(self, vlist):
630 # type: (value.List) -> MeasuredDoc
631 """Print a string literal."""
632 if len(vlist.items) == 0:
633 return _Text("[]")
634 mdocs = [self._Value(item) for item in vlist.items]
635 return self._Surrounded("[", self._Tabular(mdocs, ","), "]")
636
637 def _YshDict(self, vdict):
638 # type: (value.Dict) -> MeasuredDoc
639 if len(vdict.d) == 0:
640 return _Text("{}")
641 mdocs = [] # type: List[MeasuredDoc]
642 for k, v in iteritems(vdict.d):
643 mdocs.append(
644 _Concat([self._DictKey(k),
645 _Text(": "),
646 self._Value(v)]))
647 return self._Surrounded("{", self._Join(mdocs, ",", " "), "}")
648
649 def _BashArray(self, varray):
650 # type: (value.BashArray) -> MeasuredDoc
651 type_name = self._Styled(self.type_style, _Text("BashArray"))
652 if len(varray.strs) == 0:
653 return _Concat([_Text("("), type_name, _Text(")")])
654 mdocs = [] # type: List[MeasuredDoc]
655 for s in varray.strs:
656 if s is None:
657 mdocs.append(_Text("null"))
658 else:
659 mdocs.append(self._BashStringLiteral(s))
660 return self._SurroundedAndPrefixed("(", type_name, " ",
661 self._Tabular(mdocs, ""), ")")
662
663 def _BashAssoc(self, vassoc):
664 # type: (value.BashAssoc) -> MeasuredDoc
665 type_name = self._Styled(self.type_style, _Text("BashAssoc"))
666 if len(vassoc.d) == 0:
667 return _Concat([_Text("("), type_name, _Text(")")])
668 mdocs = [] # type: List[MeasuredDoc]
669 for k2, v2 in iteritems(vassoc.d):
670 mdocs.append(
671 _Concat([
672 _Text("["),
673 self._BashStringLiteral(k2),
674 _Text("]="),
675 self._BashStringLiteral(v2)
676 ]))
677 return self._SurroundedAndPrefixed("(", type_name, " ",
678 self._Join(mdocs, "", " "), ")")
679
680 def _SparseArray(self, val):
681 # type: (value.SparseArray) -> MeasuredDoc
682 type_name = self._Styled(self.type_style, _Text("SparseArray"))
683 if len(val.d) == 0:
684 return _Concat([_Text("("), type_name, _Text(")")])
685 mdocs = [] # type: List[MeasuredDoc]
686 for k2, v2 in iteritems(val.d):
687 mdocs.append(
688 _Concat([
689 _Text("["),
690 self._Styled(self.int_style, _Text(mops.ToStr(k2))),
691 _Text("]="),
692 self._BashStringLiteral(v2)
693 ]))
694 return self._SurroundedAndPrefixed("(", type_name, " ",
695 self._Join(mdocs, "", " "), ")")
696
697 def _Value(self, val):
698 # type: (value_t) -> MeasuredDoc
699
700 with tagswitch(val) as case:
701 if case(value_e.Null):
702 return self._Styled(self.null_style, _Text("null"))
703
704 elif case(value_e.Bool):
705 b = cast(value.Bool, val).b
706 return self._Styled(self.bool_style,
707 _Text("true" if b else "false"))
708
709 elif case(value_e.Int):
710 i = cast(value.Int, val).i
711 return self._Styled(self.int_style, _Text(mops.ToStr(i)))
712
713 elif case(value_e.Float):
714 f = cast(value.Float, val).f
715 return self._Styled(self.float_style, _Text(_FloatString(f)))
716
717 elif case(value_e.Str):
718 s = cast(value.Str, val).s
719 return self._StringLiteral(s)
720
721 elif case(value_e.Range):
722 r = cast(value.Range, val)
723 type_name = self._Styled(self.type_style, _Text(ValType(r)))
724 mdocs = [_Text(str(r.lower)), _Text(".."), _Text(str(r.upper))]
725 return self._SurroundedAndPrefixed("(", type_name, " ",
726 self._Join(mdocs, "", " "),
727 ")")
728
729 elif case(value_e.List):
730 vlist = cast(value.List, val)
731 heap_id = j8.HeapValueId(vlist)
732 if self.visiting.get(heap_id, False):
733 return _Concat([
734 _Text("["),
735 self._Styled(self.cycle_style, _Text("...")),
736 _Text("]")
737 ])
738 else:
739 self.visiting[heap_id] = True
740 result = self._YshList(vlist)
741 self.visiting[heap_id] = False
742 return result
743
744 elif case(value_e.Dict):
745 vdict = cast(value.Dict, val)
746 heap_id = j8.HeapValueId(vdict)
747 if self.visiting.get(heap_id, False):
748 return _Concat([
749 _Text("{"),
750 self._Styled(self.cycle_style, _Text("...")),
751 _Text("}")
752 ])
753 else:
754 self.visiting[heap_id] = True
755 result = self._YshDict(vdict)
756 self.visiting[heap_id] = False
757 return result
758
759 elif case(value_e.SparseArray):
760 sparse = cast(value.SparseArray, val)
761 return self._SparseArray(sparse)
762
763 elif case(value_e.BashArray):
764 varray = cast(value.BashArray, val)
765 return self._BashArray(varray)
766
767 elif case(value_e.BashAssoc):
768 vassoc = cast(value.BashAssoc, val)
769 return self._BashAssoc(vassoc)
770
771 else:
772 type_name = self._Styled(self.type_style, _Text(ValType(val)))
773 id_str = j8.ValueIdString(val)
774 return _Concat([_Text("<"), type_name, _Text(id_str + ">")])
775
776
777# vim: sw=4