src.elv.sh@v0.21.0-dev.0.20240515223629-06979efb9a2a/pkg/eval/vals/struct_map.go (about) 1 package vals 2 3 import ( 4 "reflect" 5 "sync" 6 7 "src.elv.sh/pkg/strutil" 8 ) 9 10 // StructMap may be implemented by a struct to make it accessible to Elvish code 11 // as a map. Each exported, named field and getter method (a method taking no 12 // argument and returning one value) becomes a field of the map, with the name 13 // mapped to dash-case. 14 // 15 // Struct maps are indistinguishable from normal maps for Elvish code. The 16 // operations Kind, Repr, Hash, Equal, Len, Index, HasKey and IterateKeys handle 17 // struct maps consistently with maps; the Assoc and Dissoc operations convert 18 // struct maps to maps. 19 // 20 // Example: 21 // 22 // type someStruct struct { 23 // // Provides the "foo-bar" field 24 // FooBar int 25 // lorem string 26 // } 27 // 28 // // Marks someStruct as a struct map 29 // func (someStruct) IsStructMap() { } 30 // 31 // // Provides the "ipsum" field 32 // func (s SomeStruct) Ipsum() string { return s.lorem } 33 // 34 // // Not a getter method; doesn't provide any field 35 // func (s SomeStruct) OtherMethod(int) { } 36 type StructMap interface{ IsStructMap() } 37 38 func promoteToMap(v StructMap) Map { 39 m := EmptyMap 40 for it := iterateStructMap(v); it.HasElem(); it.Next() { 41 m = m.Assoc(it.Elem()) 42 } 43 return m 44 } 45 46 // PseudoMap may be implemented by a type to support map-like introspection. The 47 // Repr, Index, HasKey and IterateKeys operations handle pseudo maps. 48 type PseudoMap interface{ Fields() StructMap } 49 50 // Keeps cached information about a structMap. 51 type structMapInfo struct { 52 filledFields int 53 plainFields int 54 // Dash-case names for all fields. The first plainFields elements 55 // corresponds to all the plain fields, while the rest corresponds to getter 56 // fields. May contain empty strings if the corresponding field is not 57 // reflected onto the structMap (i.e. unexported fields, unexported methods 58 // and non-getter methods). 59 fieldNames []string 60 } 61 62 var structMapInfos sync.Map 63 64 // Gets the structMapInfo associated with a type, caching the result. 65 func getStructMapInfo(t reflect.Type) structMapInfo { 66 if info, ok := structMapInfos.Load(t); ok { 67 return info.(structMapInfo) 68 } 69 info := makeStructMapInfo(t) 70 structMapInfos.Store(t, info) 71 return info 72 } 73 74 func makeStructMapInfo(t reflect.Type) structMapInfo { 75 n := t.NumField() 76 m := t.NumMethod() 77 fieldNames := make([]string, n+m) 78 filledFields := 0 79 80 for i := 0; i < n; i++ { 81 field := t.Field(i) 82 if field.PkgPath == "" && !field.Anonymous { 83 fieldNames[i] = strutil.CamelToDashed(field.Name) 84 filledFields++ 85 } 86 } 87 88 for i := 0; i < m; i++ { 89 method := t.Method(i) 90 if method.PkgPath == "" && method.Type.NumIn() == 1 && method.Type.NumOut() == 1 { 91 fieldNames[i+n] = strutil.CamelToDashed(method.Name) 92 filledFields++ 93 } 94 } 95 96 return structMapInfo{filledFields, n, fieldNames} 97 } 98 99 type structMapIterator struct { 100 m reflect.Value 101 info structMapInfo 102 index int 103 } 104 105 func iterateStructMap(m StructMap) *structMapIterator { 106 it := &structMapIterator{reflect.ValueOf(m), getStructMapInfo(reflect.TypeOf(m)), 0} 107 it.fixIndex() 108 return it 109 } 110 111 func (it *structMapIterator) fixIndex() { 112 fieldNames := it.info.fieldNames 113 for it.index < len(fieldNames) && fieldNames[it.index] == "" { 114 it.index++ 115 } 116 } 117 118 func (it *structMapIterator) Elem() (any, any) { 119 return it.elem() 120 } 121 122 func (it *structMapIterator) elem() (string, any) { 123 name := it.info.fieldNames[it.index] 124 if it.index < it.info.plainFields { 125 return name, it.m.Field(it.index).Interface() 126 } 127 method := it.m.Method(it.index - it.info.plainFields) 128 return name, method.Call(nil)[0].Interface() 129 } 130 131 func (it *structMapIterator) HasElem() bool { 132 return it.index < len(it.info.fieldNames) 133 } 134 135 func (it *structMapIterator) Next() { 136 it.index++ 137 it.fixIndex() 138 }