File: struct_map.go

package info (click to toggle)
elvish 0.21.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 6,372 kB
  • sloc: javascript: 236; sh: 130; python: 104; makefile: 88; xml: 9
file content (138 lines) | stat: -rw-r--r-- 3,681 bytes parent folder | download
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()
}