1// Package typeutil provides utility functions for converting between different types2// and checking their states. It aims to provide consistent behavior across different3// types while remaining lightweight and dependency-free.4package typeutil56import (7 "errors"8 "sort"9 "strconv"10 "strings"11 "time"12)1314// stringer is the interface that wraps the String method.15type stringer interface {16 String() string17}1819// ToString converts any value to its string representation.20// It supports a wide range of Go types including:21// - Basic: string, bool22// - Numbers: int, int8-64, uint, uint8-64, float32, float6423// - Special: time.Time, address, []byte24// - Slices: []T for most basic types25// - Maps: map[string]string, map[string]any26// - Interface: types implementing String() string27//28// Example usage:29//30// str := typeutil.ToString(42) // "42"31// str = typeutil.ToString([]int{1, 2}) // "[1 2]"32// str = typeutil.ToString(map[string]string{ // "map[a:1 b:2]"33// "a": "1",34// "b": "2",35// })36func ToString(val any) string {37 if val == nil {38 return ""39 }4041 // First check if value implements Stringer interface42 if s, ok := val.(interface{ String() string }); ok {43 return s.String()44 }4546 switch v := val.(type) {47 // Pointer types - dereference and recurse48 case *string:49 if v == nil {50 return ""51 }52 return *v53 case *int:54 if v == nil {55 return ""56 }57 return strconv.Itoa(*v)58 case *bool:59 if v == nil {60 return ""61 }62 return strconv.FormatBool(*v)63 case *time.Time:64 if v == nil {65 return ""66 }67 return v.String()68 case *address:69 if v == nil {70 return ""71 }72 return string(*v)7374 // String types75 case string:76 return v77 case stringer:78 return v.String()7980 // Special types81 case time.Time:82 return v.String()83 case address:84 return string(v)85 case []byte:86 return string(v)87 case struct{}:88 return "{}"8990 // Integer types91 case int:92 return strconv.Itoa(v)93 case int8:94 return strconv.FormatInt(int64(v), 10)95 case int16:96 return strconv.FormatInt(int64(v), 10)97 case int32:98 return strconv.FormatInt(int64(v), 10)99 case int64:100 return strconv.FormatInt(v, 10)101 case uint:102 return strconv.FormatUint(uint64(v), 10)103 case uint8:104 return strconv.FormatUint(uint64(v), 10)105 case uint16:106 return strconv.FormatUint(uint64(v), 10)107 case uint32:108 return strconv.FormatUint(uint64(v), 10)109 case uint64:110 return strconv.FormatUint(v, 10)111112 // Float types113 case float32:114 return strconv.FormatFloat(float64(v), 'f', -1, 32)115 case float64:116 return strconv.FormatFloat(v, 'f', -1, 64)117118 // Boolean119 case bool:120 if v {121 return "true"122 }123 return "false"124125 // Slice types126 case []string:127 return join(v)128 case []int:129 return join(v)130 case []int32:131 return join(v)132 case []int64:133 return join(v)134 case []float32:135 return join(v)136 case []float64:137 return join(v)138 case []any:139 return join(v)140 case []time.Time:141 return joinTimes(v)142 case []stringer:143 return join(v)144 case []address:145 return joinAddresses(v)146 case [][]byte:147 return joinBytes(v)148149 // Map types with various key types150 case map[any]any, map[string]any, map[string]string, map[string]int:151 var b strings.Builder152 b.WriteString("map[")153 first := true154155 switch m := v.(type) {156 case map[any]any:157 // Convert all keys to strings for consistent ordering158 keys := make([]string, 0)159 keyMap := make(map[string]any)160161 for k := range m {162 keyStr := ToString(k)163 keys = append(keys, keyStr)164 keyMap[keyStr] = k165 }166 sort.Strings(keys)167168 for _, keyStr := range keys {169 if !first {170 b.WriteString(" ")171 }172 origKey := keyMap[keyStr]173 b.WriteString(keyStr)174 b.WriteString(":")175 b.WriteString(ToString(m[origKey]))176 first = false177 }178179 case map[string]any:180 keys := make([]string, 0)181 for k := range m {182 keys = append(keys, k)183 }184 sort.Strings(keys)185186 for _, k := range keys {187 if !first {188 b.WriteString(" ")189 }190 b.WriteString(k)191 b.WriteString(":")192 b.WriteString(ToString(m[k]))193 first = false194 }195196 case map[string]string:197 keys := make([]string, 0)198 for k := range m {199 keys = append(keys, k)200 }201 sort.Strings(keys)202203 for _, k := range keys {204 if !first {205 b.WriteString(" ")206 }207 b.WriteString(k)208 b.WriteString(":")209 b.WriteString(m[k])210 first = false211 }212213 case map[string]int:214 keys := make([]string, 0)215 for k := range m {216 keys = append(keys, k)217 }218 sort.Strings(keys)219220 for _, k := range keys {221 if !first {222 b.WriteString(" ")223 }224 b.WriteString(k)225 b.WriteString(":")226 b.WriteString(strconv.Itoa(m[k]))227 first = false228 }229 }230 b.WriteString("]")231 return b.String()232233 // Default234 default:235 return "<unknown>"236 }237}238239func join(slice any) string {240 if IsZero(slice) {241 return "[]"242 }243244 items := ToInterfaceSlice(slice)245 if items == nil {246 return "[]"247 }248249 var b strings.Builder250 b.WriteString("[")251 for i, item := range items {252 if i > 0 {253 b.WriteString(" ")254 }255 b.WriteString(ToString(item))256 }257 b.WriteString("]")258 return b.String()259}260261func joinTimes(slice []time.Time) string {262 if len(slice) == 0 {263 return "[]"264 }265 var b strings.Builder266 b.WriteString("[")267 for i, t := range slice {268 if i > 0 {269 b.WriteString(" ")270 }271 b.WriteString(t.String())272 }273 b.WriteString("]")274 return b.String()275}276277func joinAddresses(slice []address) string {278 if len(slice) == 0 {279 return "[]"280 }281 var b strings.Builder282 b.WriteString("[")283 for i, addr := range slice {284 if i > 0 {285 b.WriteString(" ")286 }287 b.WriteString(string(addr))288 }289 b.WriteString("]")290 return b.String()291}292293func joinBytes(slice [][]byte) string {294 if len(slice) == 0 {295 return "[]"296 }297 var b strings.Builder298 b.WriteString("[")299 for i, bytes := range slice {300 if i > 0 {301 b.WriteString(" ")302 }303 b.WriteString(string(bytes))304 }305 b.WriteString("]")306 return b.String()307}308309// ToBool converts any value to a boolean based on common programming conventions.310// For example:311// - Numbers: 0 is false, any other number is true312// - Strings: "", "0", "false", "f", "no", "n", "off" are false, others are true313// - Slices/Maps: empty is false, non-empty is true314// - nil: always false315// - bool: direct value316func ToBool(val any) bool {317 if IsZero(val) {318 return false319 }320321 // Handle special string cases322 if str, ok := val.(string); ok {323 str = strings.ToLower(strings.TrimSpace(str))324 return str != "" && str != "0" && str != "false" && str != "f" && str != "no" && str != "n" && str != "off"325 }326327 return true328}329330// IsZero returns true if the value represents a "zero" or "empty" state for its type.331// For example:332// - Numbers: 0333// - Strings: ""334// - Slices/Maps: empty335// - nil: true336// - bool: false337// - time.Time: IsZero()338// - address: empty string339func IsZero(val any) bool {340 if val == nil {341 return true342 }343344 switch v := val.(type) {345 // Pointer types - nil pointer is zero, otherwise check pointed value346 case *bool:347 return v == nil || !*v348 case *string:349 return v == nil || *v == ""350 case *int:351 return v == nil || *v == 0352 case *time.Time:353 return v == nil || v.IsZero()354 case *address:355 return v == nil || string(*v) == ""356357 // Bool358 case bool:359 return !v360361 // String types362 case string:363 return v == ""364 case stringer:365 return v.String() == ""366367 // Integer types368 case int:369 return v == 0370 case int8:371 return v == 0372 case int16:373 return v == 0374 case int32:375 return v == 0376 case int64:377 return v == 0378 case uint:379 return v == 0380 case uint8:381 return v == 0382 case uint16:383 return v == 0384 case uint32:385 return v == 0386 case uint64:387 return v == 0388389 // Float types390 case float32:391 return v == 0392 case float64:393 return v == 0394395 // Special types396 case []byte:397 return len(v) == 0398 case time.Time:399 return v.IsZero()400 case address:401 return string(v) == ""402403 // Slices (check if empty)404 case []string:405 return len(v) == 0406 case []int:407 return len(v) == 0408 case []int32:409 return len(v) == 0410 case []int64:411 return len(v) == 0412 case []float32:413 return len(v) == 0414 case []float64:415 return len(v) == 0416 case []any:417 return len(v) == 0418 case []time.Time:419 return len(v) == 0420 case []address:421 return len(v) == 0422 case [][]byte:423 return len(v) == 0424 case []stringer:425 return len(v) == 0426427 // Maps (check if empty)428 case map[string]string:429 return len(v) == 0430 case map[string]any:431 return len(v) == 0432433 default:434 return false // non-nil unknown types are considered non-zero435 }436}437438// ToInterfaceSlice converts various slice types to []any439func ToInterfaceSlice(val any) []any {440 switch v := val.(type) {441 case []any:442 return v443 case []string:444 result := make([]any, len(v))445 for i, s := range v {446 result[i] = s447 }448 return result449 case []int:450 result := make([]any, len(v))451 for i, n := range v {452 result[i] = n453 }454 return result455 case []int32:456 result := make([]any, len(v))457 for i, n := range v {458 result[i] = n459 }460 return result461 case []int64:462 result := make([]any, len(v))463 for i, n := range v {464 result[i] = n465 }466 return result467 case []float32:468 result := make([]any, len(v))469 for i, n := range v {470 result[i] = n471 }472 return result473 case []float64:474 result := make([]any, len(v))475 for i, n := range v {476 result[i] = n477 }478 return result479 case []bool:480 result := make([]any, len(v))481 for i, b := range v {482 result[i] = b483 }484 return result485 default:486 return nil487 }488}489490// ToMapStringInterface converts a map with string keys and any value type to map[string]any491func ToMapStringInterface(m any) (map[string]any, error) {492 result := make(map[string]any)493494 switch v := m.(type) {495 case map[string]any:496 return v, nil497 case map[string]string:498 for k, val := range v {499 result[k] = val500 }501 case map[string]int:502 for k, val := range v {503 result[k] = val504 }505 case map[string]int64:506 for k, val := range v {507 result[k] = val508 }509 case map[string]float64:510 for k, val := range v {511 result[k] = val512 }513 case map[string]bool:514 for k, val := range v {515 result[k] = val516 }517 case map[string][]string:518 for k, val := range v {519 result[k] = ToInterfaceSlice(val)520 }521 case map[string][]int:522 for k, val := range v {523 result[k] = ToInterfaceSlice(val)524 }525 case map[string][]any:526 for k, val := range v {527 result[k] = val528 }529 case map[string]map[string]any:530 for k, val := range v {531 result[k] = val532 }533 case map[string]map[string]string:534 for k, val := range v {535 if converted, err := ToMapStringInterface(val); err == nil {536 result[k] = converted537 } else {538 return nil, errors.New("failed to convert nested map at key: " + k)539 }540 }541 default:542 return nil, errors.New("unsupported map type: " + ToString(m))543 }544545 return result, nil546}547548// ToMapIntInterface converts a map with int keys and any value type to map[int]any549func ToMapIntInterface(m any) (map[int]any, error) {550 result := make(map[int]any)551552 switch v := m.(type) {553 case map[int]any:554 return v, nil555 case map[int]string:556 for k, val := range v {557 result[k] = val558 }559 case map[int]int:560 for k, val := range v {561 result[k] = val562 }563 case map[int]int64:564 for k, val := range v {565 result[k] = val566 }567 case map[int]float64:568 for k, val := range v {569 result[k] = val570 }571 case map[int]bool:572 for k, val := range v {573 result[k] = val574 }575 case map[int][]string:576 for k, val := range v {577 result[k] = ToInterfaceSlice(val)578 }579 case map[int][]int:580 for k, val := range v {581 result[k] = ToInterfaceSlice(val)582 }583 case map[int][]any:584 for k, val := range v {585 result[k] = val586 }587 case map[int]map[string]any:588 for k, val := range v {589 result[k] = val590 }591 case map[int]map[int]any:592 for k, val := range v {593 result[k] = val594 }595 default:596 return nil, errors.New("unsupported map type: " + ToString(m))597 }598599 return result, nil600}601602// ToStringSlice converts various slice types to []string603func ToStringSlice(val any) []string {604 switch v := val.(type) {605 case []string:606 return v607 case []any:608 result := make([]string, len(v))609 for i, item := range v {610 result[i] = ToString(item)611 }612 return result613 case []int:614 result := make([]string, len(v))615 for i, n := range v {616 result[i] = strconv.Itoa(n)617 }618 return result619 case []int32:620 result := make([]string, len(v))621 for i, n := range v {622 result[i] = strconv.FormatInt(int64(n), 10)623 }624 return result625 case []int64:626 result := make([]string, len(v))627 for i, n := range v {628 result[i] = strconv.FormatInt(n, 10)629 }630 return result631 case []float32:632 result := make([]string, len(v))633 for i, n := range v {634 result[i] = strconv.FormatFloat(float64(n), 'f', -1, 32)635 }636 return result637 case []float64:638 result := make([]string, len(v))639 for i, n := range v {640 result[i] = strconv.FormatFloat(n, 'f', -1, 64)641 }642 return result643 case []bool:644 result := make([]string, len(v))645 for i, b := range v {646 result[i] = strconv.FormatBool(b)647 }648 return result649 case []time.Time:650 result := make([]string, len(v))651 for i, t := range v {652 result[i] = t.String()653 }654 return result655 case []address:656 result := make([]string, len(v))657 for i, addr := range v {658 result[i] = string(addr)659 }660 return result661 case [][]byte:662 result := make([]string, len(v))663 for i, b := range v {664 result[i] = string(b)665 }666 return result667 case []stringer:668 result := make([]string, len(v))669 for i, s := range v {670 result[i] = s.String()671 }672 return result673 case []uint:674 result := make([]string, len(v))675 for i, n := range v {676 result[i] = strconv.FormatUint(uint64(n), 10)677 }678 return result679 case []uint8:680 result := make([]string, len(v))681 for i, n := range v {682 result[i] = strconv.FormatUint(uint64(n), 10)683 }684 return result685 case []uint16:686 result := make([]string, len(v))687 for i, n := range v {688 result[i] = strconv.FormatUint(uint64(n), 10)689 }690 return result691 case []uint32:692 result := make([]string, len(v))693 for i, n := range v {694 result[i] = strconv.FormatUint(uint64(n), 10)695 }696 return result697 case []uint64:698 result := make([]string, len(v))699 for i, n := range v {700 result[i] = strconv.FormatUint(n, 10)701 }702 return result703 default:704 // Try to convert using reflection if it's a slice705 if slice := ToInterfaceSlice(val); slice != nil {706 result := make([]string, len(slice))707 for i, item := range slice {708 result[i] = ToString(item)709 }710 return result711 }712 return nil713 }714}715Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.