github.com/mitranim/gg@v0.1.17/grepr/grepr_internal.go (about) 1 package grepr 2 3 import ( 4 "fmt" 5 "math" 6 r "reflect" 7 "regexp" 8 "strconv" 9 "strings" 10 "unicode" 11 "unicode/utf8" 12 13 "github.com/mitranim/gg" 14 ) 15 16 var ( 17 typRtype = gg.Type[r.Type]() 18 typBool = gg.Type[bool]() 19 typInt = gg.Type[int]() 20 typString = gg.Type[string]() 21 typFloat64 = gg.Type[float64]() 22 typComplex64 = gg.Type[complex64]() 23 typComplex128 = gg.Type[complex128]() 24 typGoStringer = gg.Type[fmt.GoStringer]() 25 ) 26 27 func (self *Fmt) fmtAny(typ r.Type, src r.Value) { 28 if self.fmtedNil(typ, src) || self.fmtedGoString(src) { 29 return 30 } 31 32 if typ == typRtype { 33 self.fmtReflectType(src) 34 return 35 } 36 37 switch src.Kind() { 38 case r.Invalid: 39 self.fmtNil(typ, src) 40 case r.Bool: 41 self.fmtBool(typ, src) 42 case r.Int8: 43 self.fmtInt64(typ, src) 44 case r.Int16: 45 self.fmtInt64(typ, src) 46 case r.Int32: 47 self.fmtInt64(typ, src) 48 case r.Int64: 49 self.fmtInt64(typ, src) 50 case r.Int: 51 self.fmtInt64(typ, src) 52 case r.Uint8: 53 self.fmtByteHex(typ, src) 54 case r.Uint16: 55 self.fmtUint64(typ, src) 56 case r.Uint32: 57 self.fmtUint64(typ, src) 58 case r.Uint64: 59 self.fmtUint64(typ, src) 60 case r.Uint: 61 self.fmtUint64(typ, src) 62 case r.Uintptr: 63 self.fmtUintHex(typ, src) 64 case r.Float32: 65 self.fmtFloat64(typ, src) 66 case r.Float64: 67 self.fmtFloat64(typ, src) 68 case r.Complex64: 69 self.fmtComplex(typ, src) 70 case r.Complex128: 71 self.fmtComplex(typ, src) 72 case r.Array: 73 self.fmtArray(src) 74 case r.Slice: 75 self.fmtSlice(typ, src) 76 case r.Chan: 77 self.fmtChan(typ, src) 78 case r.Func: 79 self.fmtFunc(typ, src) 80 case r.Interface: 81 self.fmtIface(typ, src) 82 case r.Map: 83 self.fmtMap(typ, src) 84 case r.Pointer: 85 self.fmtPointer(typ, src) 86 case r.UnsafePointer: 87 self.fmtUnsafePointer(typ, src) 88 case r.String: 89 self.fmtString(typ, src) 90 case r.Struct: 91 self.fmtStruct(src) 92 default: 93 panic(gg.Errf(`unrecognized reflect kind %q`, src.Kind())) 94 } 95 } 96 97 func (self *Fmt) fmtedPointerVisited(typ r.Type, src r.Value) bool { 98 if src.IsNil() { 99 self.fmtNil(typ, src) 100 return true 101 } 102 103 ptr := src.UnsafePointer() 104 _, ok := self.Visited[ptr] 105 if ok { 106 self.fmtPointerVisited(src) 107 return true 108 } 109 110 self.Visited.Init().Add(ptr) 111 return false 112 } 113 114 func (self *Fmt) fmtPointerVisited(src r.Value) { 115 self.AppendString(`/* visited */ `) 116 self.fmtType(src.Type()) 117 self.AppendByte('(') 118 self.fmtUint64Hex(uint64(src.Pointer())) 119 self.AppendByte(')') 120 } 121 122 func (self *Fmt) fmtedNil(typ r.Type, src r.Value) bool { 123 if !src.IsValid() || gg.IsValueNil(src) { 124 self.fmtNil(typ, src) 125 return true 126 } 127 return false 128 } 129 130 func (self *Fmt) fmtNil(typ r.Type, src r.Value) { 131 srcTyp := gg.ValueType(src) 132 133 // TODO simplify. 134 if !(isTypeInterface(typ) && isValueNilInterface(src) || 135 typ == srcTyp && gg.IsTypeNilable(typ)) { 136 defer self.fmtConvOpen(derefIface(src).Type()).fmtConvClose() 137 } 138 139 self.AppendString(`nil`) 140 } 141 142 /* 143 TODO: consider custom interface such as `.AppendGoString`, possibly with 144 indentation support. 145 146 TODO: if, rather than implementing `.GoString` directly, the input inherits the 147 method from an embedded type, we should do nothing and return false. 148 */ 149 func (self *Fmt) fmtedGoString(src r.Value) bool { 150 if !src.Type().Implements(typGoStringer) { 151 return false 152 } 153 self.AppendString(src.Interface().(fmt.GoStringer).GoString()) 154 return true 155 } 156 157 func (self *Fmt) fmtBool(typ r.Type, src r.Value) { 158 defer self.fmtConvOpt(typ, src.Type(), typBool).fmtConvClose() 159 self.AppendBool(src.Bool()) 160 } 161 162 /* 163 Adapted from `strconv.FormatComplex` with minor changes. We don't bother 164 printing the real part when it's zero, and we avoid the scientific notation 165 when formatting floats. 166 */ 167 func (self *Fmt) fmtComplex(typ r.Type, src r.Value) { 168 done := self.fmtConvOpt(typ, src.Type(), typComplex128) 169 if done != nil { 170 defer done.fmtConvClose() 171 } else { 172 self.AppendByte('(') 173 defer self.AppendByte(')') 174 } 175 176 val := src.Complex() 177 realPart := real(val) 178 imagPart := imag(val) 179 180 if realPart == 0 { 181 self.AppendFloat64(imagPart) 182 self.AppendByte('i') 183 return 184 } 185 186 self.AppendFloat64(realPart) 187 if !(imagPart < 0) { 188 self.AppendByte('+') 189 } 190 191 self.AppendFloat64(imagPart) 192 self.AppendByte('i') 193 } 194 195 func (self *Fmt) fmtInt64(typ r.Type, src r.Value) { 196 defer self.fmtConvOpt(typ, src.Type(), typInt).fmtConvClose() 197 self.AppendInt64(src.Int()) 198 } 199 200 func (self *Fmt) fmtUint64(typ r.Type, src r.Value) { 201 defer self.fmtConvOpt(typ, src.Type()).fmtConvClose() 202 self.AppendUint64(src.Uint()) 203 } 204 205 func (self *Fmt) fmtByteHex(typ r.Type, src r.Value) { 206 defer self.fmtConvOpt(typ, src.Type()).fmtConvClose() 207 self.AppendString(`0x`) 208 self.AppendByteHex(byte(src.Uint())) 209 } 210 211 func (self *Fmt) fmtUintHex(typ r.Type, src r.Value) { 212 defer self.fmtConvOpt(typ, src.Type()).fmtConvClose() 213 self.fmtUint64Hex(src.Uint()) 214 } 215 216 func (self *Fmt) fmtFloat64(typ r.Type, src r.Value) { 217 val := src.Float() 218 srcTyp := src.Type() 219 if isTypeInterface(typ) && (srcTyp != typFloat64 || !(math.Remainder(val, 1) > 0)) { 220 defer self.fmtConvOpen(srcTyp).fmtConvClose() 221 } 222 223 if val < 0 { 224 if math.IsInf(val, -1) { 225 self.AppendString(`math.Inf(-1)`) 226 return 227 } 228 self.AppendFloat64(val) 229 return 230 } 231 232 if val >= 0 { 233 if math.IsInf(val, 1) { 234 self.AppendString(`math.Inf(0)`) 235 return 236 } 237 self.AppendFloat64(val) 238 return 239 } 240 241 self.AppendString(`math.NaN()`) 242 } 243 244 func (self *Fmt) fmtString(typ r.Type, src r.Value) { 245 defer self.fmtConvOpt(typ, src.Type(), typString).fmtConvClose() 246 text := src.String() 247 self.fmtStringInner(text, textPrintability(text)) 248 } 249 250 func (self *Fmt) fmtStringInner(src string, prn printability) { 251 /** 252 For the most part we're more restrictive than `strconv.CanBackquote`, but 253 unlike `strconv.CanBackquote` we allow '\n' in backquoted strings. We want 254 to avoid loss of information when a multiline string is displayed in a 255 terminal and copied to an editor. `strconv.CanBackquote` allows too many 256 characters which may fail to display properly. This includes the tabulation 257 character '\t', which is usually converted to spaces, and a variety of 258 Unicode code points without corresponding graphical symbols. On the other 259 hand, we want to support multiline strings in the common case, without edge 260 case breakage. We assume that terminals, and other means of displaying the 261 output of a program that may be using `grepr`, do not convert printed '\n' 262 to '\r' or "\r\n", but may convert printed '\r' or "\r\n" to '\n', as Unix 263 line endings tend to be preferred by any tooling used by Go developers. This 264 allows us to support displaying strings as multiline in the common case, 265 while avoiding information loss in the case of strings with '\r'. 266 */ 267 if !prn.errors && !prn.unprintables && !prn.backquotes && !prn.carriageReturns && (!prn.lineFeeds || self.IsMulti()) { 268 self.AppendByte('`') 269 self.AppendString(src) 270 self.AppendByte('`') 271 } else { 272 self.Buf = strconv.AppendQuote(self.Buf, src) 273 } 274 } 275 276 func (self *Fmt) fmtSlice(typ r.Type, src r.Value) { 277 if self.fmtedNil(typ, src) { 278 return 279 } 280 281 if gg.IsValueBytes(src) { 282 self.fmtBytes(typ, src) 283 return 284 } 285 286 self.fmtArray(src) 287 } 288 289 func (self *Fmt) fmtArray(src r.Value) { 290 typ := src.Type() 291 292 self.fmtTypeOpt(typ) 293 defer self.setElideType(!isTypeInterface(typ.Elem())).Done() 294 295 if src.Len() == 0 || src.IsZero() { 296 self.AppendString(`{}`) 297 return 298 } 299 300 if self.IsSingle() { 301 self.fmtArraySingle(typ, src) 302 return 303 } 304 305 self.fmtArrayMulti(typ, src) 306 } 307 308 func (self *Fmt) fmtArraySingle(typ r.Type, src r.Value) { 309 typElem := typ.Elem() 310 311 self.AppendByte('{') 312 for ind := range gg.Iter(src.Len()) { 313 if ind > 0 { 314 self.AppendString(`, `) 315 } 316 self.fmtAny(typElem, src.Index(ind)) 317 } 318 self.AppendByte('}') 319 } 320 321 func (self *Fmt) fmtArrayMulti(typ r.Type, src r.Value) { 322 typElem := typ.Elem() 323 324 self.AppendByte('{') 325 self.AppendNewline() 326 snap := self.lvlInc() 327 328 for ind := range gg.Iter(src.Len()) { 329 self.fmtIndent() 330 self.fmtAny(typElem, src.Index(ind)) 331 self.AppendByte(',') 332 self.AppendNewline() 333 } 334 335 snap.Done() 336 self.fmtIndent() 337 self.AppendByte('}') 338 } 339 340 func (self *Fmt) fmtBytes(typ r.Type, src r.Value) { 341 if self.fmtedNil(typ, src) { 342 return 343 } 344 345 text := src.Bytes() 346 347 if len(text) > 0 { 348 prn := textPrintability(text) 349 350 if !prn.errors && !prn.unprintables { 351 self.fmtType(src.Type()) 352 self.AppendByte('(') 353 self.fmtStringInner(gg.ToString(text), prn) 354 self.AppendByte(')') 355 return 356 } 357 } 358 359 self.fmtBytesHex(src.Type(), text) 360 } 361 362 /* 363 Similar to `.fmtArray`, but much faster and always single-line. TODO consider 364 supporting column width in `Conf`, which would allow us to print bytes in 365 rows. 366 */ 367 func (self *Fmt) fmtBytesHex(typ r.Type, src []byte) { 368 self.fmtTypeOpt(typ) 369 self.AppendByte('{') 370 for ind, val := range src { 371 if ind > 0 { 372 self.AppendString(`, `) 373 } 374 self.AppendString(`0x`) 375 self.AppendByteHex(val) 376 } 377 self.AppendByte('}') 378 } 379 380 func (self *Fmt) fmtChan(typ r.Type, src r.Value) { 381 self.fmtUnfmtable(typ, src) 382 } 383 384 func (self *Fmt) fmtFunc(typ r.Type, src r.Value) { 385 self.fmtUnfmtable(typ, src) 386 } 387 388 func (self *Fmt) fmtIface(typ r.Type, src r.Value) { 389 if self.fmtedNil(typ, src) { 390 return 391 } 392 self.fmtAny(typ, src.Elem()) 393 } 394 395 func (self *Fmt) fmtMap(typ r.Type, src r.Value) { 396 if self.fmtedNil(typ, src) { 397 return 398 } 399 400 srcTyp := src.Type() 401 402 self.fmtTypeOpt(srcTyp) 403 defer self.setElideType(!isTypeInterface(srcTyp.Elem())).Done() 404 405 if src.Len() == 0 { 406 self.AppendString(`{}`) 407 return 408 } 409 410 if self.IsSingle() { 411 self.fmtMapSingle(src) 412 return 413 } 414 415 self.fmtMapMulti(src) 416 } 417 418 func (self *Fmt) fmtMapSingle(src r.Value) { 419 typ := src.Type() 420 typKey := typ.Key() 421 typVal := typ.Elem() 422 423 self.AppendByte('{') 424 425 iter := src.MapRange() 426 var found bool 427 428 for iter.Next() { 429 if found { 430 self.AppendString(`, `) 431 } 432 found = true 433 434 self.fmtAny(typKey, iter.Key()) 435 self.AppendString(`: `) 436 self.fmtAny(typVal, iter.Value()) 437 } 438 439 self.AppendByte('}') 440 } 441 442 func (self *Fmt) fmtMapMulti(src r.Value) { 443 typ := src.Type() 444 typKey := typ.Key() 445 typVal := typ.Elem() 446 447 self.AppendByte('{') 448 self.AppendNewline() 449 450 iter := src.MapRange() 451 snap := self.lvlInc() 452 453 for iter.Next() { 454 self.fmtIndent() 455 self.fmtAny(typKey, iter.Key()) 456 self.AppendString(`: `) 457 self.fmtAny(typVal, iter.Value()) 458 self.AppendByte(',') 459 self.AppendNewline() 460 } 461 462 snap.Done() 463 self.fmtIndent() 464 self.AppendByte('}') 465 } 466 467 func (self *Fmt) fmtPointer(typ r.Type, src r.Value) { 468 if self.fmtedNil(typ, src) || self.fmtedPointerVisited(typ, src) { 469 return 470 } 471 472 defer self.setElideType(false).Done() 473 src = src.Elem() 474 475 if canAmpersand(src.Kind()) { 476 self.AppendByte('&') 477 self.fmtAny(typ, src) 478 return 479 } 480 481 self.fmtIdent(`gg`, `Ptr`) 482 self.fmtTypeArg(src.Type()) 483 self.AppendByte('(') 484 self.fmtAny(typ, src) 485 self.AppendByte(')') 486 } 487 488 func (self *Fmt) fmtUnsafePointer(typ r.Type, src r.Value) { 489 defer self.fmtConvOpt(typ, src.Type()).fmtConvClose() 490 self.fmtUint64Hex(uint64(src.Pointer())) 491 } 492 493 func (self *Fmt) fmtStruct(src r.Value) { 494 self.fmtTypeOpt(src.Type()) 495 defer self.setElideType(false).Done() 496 497 if src.NumField() == 0 { 498 self.AppendString(`{}`) 499 return 500 } 501 502 if self.IsSingle() { 503 self.fmtStructSingle(src) 504 return 505 } 506 507 self.fmtStructMulti(src) 508 } 509 510 func (self *Fmt) fmtStructField(src r.Value, field r.StructField) { 511 self.AppendString(field.Name) 512 self.AppendString(`: `) 513 self.fmtAny(field.Type, src) 514 } 515 516 func (self *Fmt) fmtStructSingle(src r.Value) { 517 if isStructUnit(src.Type()) { 518 self.fmtStructSingleAnon(src) 519 return 520 } 521 self.fmtStructSingleNamed(src) 522 } 523 524 func (self *Fmt) fmtStructSingleAnon(src r.Value) { 525 head := src.Field(0) 526 527 self.AppendByte('{') 528 if !self.skipField(head) { 529 self.fmtAny(structHeadType(src.Type()), head) 530 } 531 self.AppendByte('}') 532 } 533 534 func (self *Fmt) fmtStructSingleNamed(src r.Value) { 535 self.AppendByte('{') 536 537 var found bool 538 539 for _, field := range gg.StructPublicFieldCache.Get(src.Type()) { 540 src := src.FieldByIndex(field.Index) 541 if self.skipField(src) { 542 continue 543 } 544 545 if found { 546 self.AppendString(`, `) 547 } 548 found = true 549 550 self.fmtStructField(src, field) 551 } 552 553 self.AppendByte('}') 554 } 555 556 func (self *Fmt) fmtStructMulti(src r.Value) { 557 if isStructUnit(src.Type()) { 558 self.fmtStructMultiAnon(src) 559 return 560 } 561 self.fmtStructMultiNamed(src) 562 } 563 564 func (self *Fmt) fmtStructMultiAnon(src r.Value) { 565 head := src.Field(0) 566 567 self.AppendByte('{') 568 569 if !self.skipField(head) { 570 defer self.lvlInc().Done() 571 self.fmtAny(structHeadType(src.Type()), head) 572 } 573 574 self.AppendByte('}') 575 } 576 577 func (self *Fmt) fmtStructMultiNamed(src r.Value) { 578 fields := gg.StructPublicFieldCache.Get(src.Type()) 579 580 if self.SkipZeroFields() { 581 test := func(field r.StructField) bool { 582 return !src.FieldByIndex(field.Index).IsZero() 583 } 584 585 count := gg.Count(fields, test) 586 587 if count == 0 { 588 self.AppendString(`{}`) 589 return 590 } 591 592 if count == 1 { 593 field := gg.Find(fields, test) 594 self.fmtStructMultiNamedUnit(src.FieldByIndex(field.Index), field) 595 return 596 } 597 } 598 599 self.fmtStructMultiNamedLines(src, fields) 600 } 601 602 func (self *Fmt) fmtStructMultiNamedUnit(src r.Value, field r.StructField) { 603 self.AppendByte('{') 604 self.fmtStructField(src, field) 605 self.AppendByte('}') 606 } 607 608 func (self *Fmt) fmtStructMultiNamedLines(src r.Value, fields []r.StructField) { 609 self.AppendByte('{') 610 self.AppendNewline() 611 snap := self.lvlInc() 612 613 for _, field := range fields { 614 src := src.FieldByIndex(field.Index) 615 if self.skipField(src) { 616 continue 617 } 618 619 self.fmtIndent() 620 self.fmtStructField(src, field) 621 self.AppendByte(',') 622 self.AppendNewline() 623 } 624 625 snap.Done() 626 self.fmtIndent() 627 self.AppendByte('}') 628 } 629 630 func (self *Fmt) fmtUnfmtable(typ r.Type, src r.Value) { 631 if self.fmtedNil(typ, src) { 632 return 633 } 634 635 self.fmtType(src.Type()) 636 self.AppendByte('(') 637 self.fmtUint64Hex(uint64(src.Pointer())) 638 self.AppendByte(')') 639 } 640 641 func (self *Fmt) fmtTypeArg(typ r.Type) { 642 if isTypeDefaultForLiteral(typ) { 643 return 644 } 645 646 self.AppendByte('[') 647 self.fmtType(typ) 648 self.AppendByte(']') 649 } 650 651 func (self *Fmt) fmtReflectType(src r.Value) { 652 self.fmtIdent(`gg`, `Type`) 653 self.AppendByte('[') 654 self.fmtType(src.Interface().(r.Type)) 655 self.AppendString(`]()`) 656 } 657 658 func (self *Fmt) fmtUint64Hex(val uint64) { 659 self.AppendString(`0x`) 660 self.AppendUint64Hex(val) 661 } 662 663 func (self *Fmt) fmtIndent() { self.AppendStringN(self.Indent, self.Lvl) } 664 665 func (self *Fmt) fmtIdent(pkg, name string) { 666 if self.Pkg != pkg { 667 self.AppendString(pkg) 668 self.AppendByte('.') 669 } 670 self.AppendString(name) 671 } 672 673 func (self *Fmt) fmtTypeOpt(typ r.Type) { 674 if !self.ElideType { 675 self.fmtType(typ) 676 } 677 } 678 679 func (self *Fmt) fmtType(typ r.Type) { 680 self.AppendString(self.typeString(typ)) 681 } 682 683 func (self *Fmt) fmtConvOpen(typ r.Type) *Fmt { 684 self.fmtType(typ) 685 self.AppendByte('(') 686 return self 687 } 688 689 func (self *Fmt) fmtConvOpt(outer, inner r.Type, excl ...r.Type) *Fmt { 690 if !isTypeInterface(outer) || gg.Has(excl, inner) { 691 return nil 692 } 693 return self.fmtConvOpen(inner) 694 } 695 696 func (self *Fmt) fmtConvClose() { 697 // The nil check is relevant for `defer`. See `.fmtConvOpt`. 698 if self != nil { 699 self.AppendByte(')') 700 } 701 } 702 703 func (self *Fmt) setElideType(val bool) gg.Snapshot[bool] { 704 return gg.SnapSwap(&self.ElideType, val) 705 } 706 707 func (self *Fmt) lvlInc() gg.Snapshot[int] { 708 return gg.SnapSwap(&self.Lvl, self.Lvl+1) 709 } 710 711 func (self *Fmt) skipField(src r.Value) bool { 712 return self.SkipZeroFields() && src.IsZero() 713 } 714 715 func (self *Fmt) typeString(typ r.Type) string { 716 return self.elidePkg(typeString(typ)) 717 } 718 719 /* 720 Known issue: this works only for `pkg.ident` but not for `[]pkg.ident`, 721 `map[pkg.ident]pkg.ident`, `func(pkg.ident)`, and so on. We should either 722 implement proper rewriting that works in all cases, or remove this feature. 723 */ 724 func (self *Fmt) elidePkg(src string) string { 725 pkg := self.Pkg 726 if pkg == `` { 727 return src 728 } 729 730 tar := strings.TrimPrefix(src, pkg) 731 if len(src) != len(tar) && len(tar) > 0 && tar[0] == '.' { 732 return tar[1:] 733 } 734 return src 735 } 736 737 func typeString(typ r.Type) string { return string(typeStringCache.Get(typ)) } 738 739 var typeStringCache = gg.TypeCacheOf[typeStringStr]() 740 741 type typeStringStr string 742 743 func (self *typeStringStr) Init(typ r.Type) { 744 if typ == nil { 745 return 746 } 747 748 tar := typ.String() 749 750 /** 751 Some types must be wrapped in parens because we use the resulting type name 752 in expression context, not in type context. Wrapping avoids ambiguity with 753 value expression syntax. 754 */ 755 if typ.Kind() == r.Func && strings.HasPrefix(tar, `func(`) || 756 typ.Kind() == r.Pointer && strings.HasPrefix(tar, `*`) { 757 tar = `(` + tar + `)` 758 } 759 760 tar = strings.ReplaceAll(tar, `interface {}`, `any`) 761 tar = reUint8.Get().ReplaceAllString(tar, `byte`) 762 *self = typeStringStr(tar) 763 } 764 765 var reUint8 = gg.NewLazy(func() *regexp.Regexp { 766 return regexp.MustCompile(`\buint8\b`) 767 }) 768 769 func canAmpersand(kind r.Kind) bool { 770 return kind == r.Array || kind == r.Slice || kind == r.Struct 771 } 772 773 func isTypeDefaultForLiteral(typ r.Type) bool { 774 switch typ { 775 case nil, typBool, typInt, typString, typComplex64, typComplex128: 776 return true 777 default: 778 return false 779 } 780 } 781 782 func isValueNilInterface(src r.Value) bool { 783 return !src.IsValid() || isValueInterface(src) && src.IsNil() 784 } 785 786 func isValueInterface(src r.Value) bool { 787 return src.Kind() == r.Interface 788 } 789 790 func isStructUnit(typ r.Type) bool { return typ.NumField() == 1 } 791 792 func isTypeInterface(typ r.Type) bool { return gg.TypeKind(typ) == r.Interface } 793 794 func structHeadType(typ r.Type) r.Type { 795 return gg.StructFieldCache.Get(typ)[0].Type 796 } 797 798 func derefIface(val r.Value) r.Value { 799 for val.Kind() == r.Interface { 800 val = val.Elem() 801 } 802 return val 803 } 804 805 func textPrintability[A gg.Text](src A) (out printability) { 806 out.init(gg.ToString(src)) 807 return 808 } 809 810 type printability struct { 811 errors, lineFeeds, carriageReturns, backquotes, escapes, unprintables bool 812 } 813 814 func (self *printability) init(src string) { 815 for _, val := range src { 816 /** 817 `unicode.IsPrint` uses `unicode.S` which includes `utf8.RuneError`. 818 As a result, it considers error runes printable, which is wildly 819 inappropriate for our purposes. So we have to handle it separately. 820 */ 821 if val == utf8.RuneError { 822 self.errors = true 823 return 824 } 825 826 if val == '\n' { 827 self.lineFeeds = true 828 continue 829 } 830 831 if val == '\r' { 832 self.carriageReturns = true 833 continue 834 } 835 836 if val == '`' { 837 self.backquotes = true 838 continue 839 } 840 841 if int(val) < len(stringEsc) && stringEsc[byte(val)] { 842 self.escapes = true 843 continue 844 } 845 846 if !unicode.IsPrint(val) { 847 self.unprintables = true 848 return 849 } 850 } 851 } 852 853 // https://go.dev/ref/spec#String_literals 854 var stringEsc = [256]bool{ 855 byte('\a'): true, 856 byte('\b'): true, 857 byte('\f'): true, 858 byte('\n'): true, 859 byte('\r'): true, 860 byte('\t'): true, 861 byte('\v'): true, 862 byte('\\'): true, 863 byte('"'): true, 864 }