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

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