src.elv.sh@v0.21.0-dev.0.20240515223629-06979efb9a2a/pkg/eval/vals/repr.go (about) 1 package vals 2 3 import ( 4 "fmt" 5 "math" 6 "math/big" 7 "sort" 8 "strconv" 9 10 "src.elv.sh/pkg/parse" 11 "src.elv.sh/pkg/persistent/hashmap" 12 ) 13 14 // Reprer wraps the Repr method. 15 type Reprer interface { 16 // Repr returns a string that represents a Value. The string either be a 17 // literal of that Value that is preferably deep-equal to it (like `[a b c]` 18 // for a list), or a string enclosed in "<>" containing the kind and 19 // identity of the Value(like `<fn 0xdeadcafe>`). 20 // 21 // If indent is at least 0, it should be pretty-printed with the current 22 // indentation level of indent; the indent of the first line has already 23 // been written and shall not be written in Repr. The returned string 24 // should never contain a trailing newline. 25 Repr(indent int) string 26 } 27 28 // ReprPlain is like Repr, but without pretty-printing. 29 func ReprPlain(v any) string { 30 return Repr(v, math.MinInt) 31 } 32 33 // Repr returns the representation for a value, a string that is preferably 34 // (but not necessarily) an Elvish expression that evaluates to the argument. 35 // The representation is pretty-printed, using indent as the initial level of 36 // indentation. It is implemented for the builtin types nil, bool and string, 37 // the File, List and Map types, StructMap types, and types satisfying the 38 // Reprer interface. For other types, it uses fmt.Sprint with the format 39 // "<unknown %v>". 40 func Repr(v any, indent int) string { 41 switch v := v.(type) { 42 case nil: 43 return "$nil" 44 case bool: 45 if v { 46 return "$true" 47 } 48 return "$false" 49 case string: 50 return parse.Quote(v) 51 case int: 52 return "(num " + strconv.Itoa(v) + ")" 53 case *big.Int: 54 return "(num " + v.String() + ")" 55 case *big.Rat: 56 return "(num " + v.String() + ")" 57 case float64: 58 return "(num " + formatFloat64(v) + ")" 59 case File: 60 return fmt.Sprintf("<file{%s %d}>", parse.Quote(v.Name()), v.Fd()) 61 case List: 62 b := NewListReprBuilder(indent) 63 for it := v.Iterator(); it.HasElem(); it.Next() { 64 b.WriteElem(Repr(it.Elem(), indent+1)) 65 } 66 return b.String() 67 case Map: 68 return reprMap(v.Iterator(), v.Len(), indent) 69 case StructMap: 70 return reprMap(iterateStructMap(v), lenStructMap(v), indent) 71 case Reprer: 72 return v.Repr(indent) 73 case PseudoMap: 74 m := v.Fields() 75 s := reprMap(iterateStructMap(m), lenStructMap(m), indent) 76 // Add a tag immediately after [. 77 return "[^" + Kind(v) + " " + s[1:] 78 default: 79 return fmt.Sprintf("<unknown %v>", v) 80 } 81 } 82 83 func reprMap(it hashmap.Iterator, n, indent int) string { 84 builder := NewMapReprBuilder(indent) 85 // Collect all the key-value pairs. 86 pairs := make([][2]any, 0, n) 87 for ; it.HasElem(); it.Next() { 88 k, v := it.Elem() 89 pairs = append(pairs, [2]any{k, v}) 90 } 91 // Sort the pairs. See the godoc of CmpTotal for the sorting algorithm. 92 sort.Slice(pairs, func(i, j int) bool { 93 return CmpTotal(pairs[i][0], pairs[j][0]) == CmpLess 94 }) 95 // Print the pairs. 96 for _, pair := range pairs { 97 k, v := pair[0], pair[1] 98 builder.WritePair(Repr(k, indent+1), indent+2, Repr(v, indent+2)) 99 } 100 return builder.String() 101 }