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