struct_map.go 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. package vals
  2. import (
  3. "reflect"
  4. "sync"
  5. "src.elv.sh/pkg/strutil"
  6. )
  7. // StructMap may be implemented by a struct to mark the struct as a "struct
  8. // map", which causes Elvish to treat it like a read-only map. Each exported,
  9. // named field and getter method (a method taking no argument and returning one
  10. // value) becomes a field of the map, with the name mapped to dash-case.
  11. //
  12. // The following operations are derived for structmaps: Kind, Repr, Hash, Len,
  13. // Index, HasKey and IterateKeys.
  14. //
  15. // Example:
  16. //
  17. // type someStruct struct {
  18. // FooBar int
  19. // lorem string
  20. // }
  21. //
  22. // func (someStruct) IsStructMap() { }
  23. //
  24. // func (s SomeStruct) Ipsum() string { return s.lorem }
  25. //
  26. // func (s SomeStruct) OtherMethod(int) { }
  27. //
  28. // An instance of someStruct behaves like a read-only map with 3 fields:
  29. // foo-bar, lorem and ipsum.
  30. type StructMap interface{ IsStructMap() }
  31. // PseudoStructMap may be implemented by a type to derive the Repr, Index,
  32. // HasKey and IterateKeys operations from the struct map returned by the Fields
  33. // method.
  34. type PseudoStructMap interface{ Fields() StructMap }
  35. // Keeps cached information about a structMap.
  36. type structMapInfo struct {
  37. filledFields int
  38. plainFields int
  39. // Dash-case names for all fields. The first plainFields elements
  40. // corresponds to all the plain fields, while the rest corresponds to getter
  41. // fields. May contain empty strings if the corresponding field is not
  42. // reflected onto the structMap (i.e. unexported fields, unexported methods
  43. // and non-getter methods).
  44. fieldNames []string
  45. }
  46. var structMapInfos sync.Map
  47. // Gets the structMapInfo associated with a type, caching the result.
  48. func getStructMapInfo(t reflect.Type) structMapInfo {
  49. if info, ok := structMapInfos.Load(t); ok {
  50. return info.(structMapInfo)
  51. }
  52. info := makeStructMapInfo(t)
  53. structMapInfos.Store(t, info)
  54. return info
  55. }
  56. func makeStructMapInfo(t reflect.Type) structMapInfo {
  57. n := t.NumField()
  58. m := t.NumMethod()
  59. fieldNames := make([]string, n+m)
  60. filledFields := 0
  61. for i := 0; i < n; i++ {
  62. field := t.Field(i)
  63. if field.PkgPath == "" && !field.Anonymous {
  64. fieldNames[i] = strutil.CamelToDashed(field.Name)
  65. filledFields++
  66. }
  67. }
  68. for i := 0; i < m; i++ {
  69. method := t.Method(i)
  70. if method.PkgPath == "" && method.Type.NumIn() == 1 && method.Type.NumOut() == 1 {
  71. fieldNames[i+n] = strutil.CamelToDashed(method.Name)
  72. filledFields++
  73. }
  74. }
  75. return structMapInfo{filledFields, n, fieldNames}
  76. }
  77. type structMapIterator struct {
  78. info structMapInfo
  79. i int
  80. }
  81. func iterateStructMap(t reflect.Type) *structMapIterator {
  82. return &structMapIterator{getStructMapInfo(t), -1}
  83. }
  84. func (it *structMapIterator) Next() bool {
  85. fields := it.info.fieldNames
  86. if it.i >= len(fields) {
  87. return false
  88. }
  89. it.i++
  90. for it.i < len(fields) && fields[it.i] == "" {
  91. it.i++
  92. }
  93. return it.i < len(fields)
  94. }
  95. func (it *structMapIterator) Get(v reflect.Value) (string, any) {
  96. name := it.info.fieldNames[it.i]
  97. if it.i < it.info.plainFields {
  98. return name, v.Field(it.i).Interface()
  99. }
  100. method := v.Method(it.i - it.info.plainFields)
  101. return name, method.Call(nil)[0].Interface()
  102. }