Pointer vs. value receivers are... interesting.
This commit is contained in:
Kistaro Windrider 2023-04-04 11:37:02 -07:00
parent 3e34e25f54
commit 99e372a4db
Signed by: kistaro
SSH Key Fingerprint: SHA256:TBE2ynfmJqsAf0CP6gsflA0q5X5wD5fVKWPsZ7eVUg8
3 changed files with 76 additions and 50 deletions

View File

@ -118,8 +118,9 @@ func (s statFunc[T]) Visible() bool {
return s.visible
}
// ExtractStats pulls all exported stats out of a struct. It puts fields before
// method.
// ExtractStats pulls all exported stats out of a struct. It puts methods before
// fields. If the calculated name of a method conflicts with the calculated
// name of a stat from a field, the method wins.
//
// A field is a stat if it is of some Stat type or is tagged with `cardsim:"stat"`,
// `cardsim:"hidden"` (invisible stat), `cardsim:"round2"` (or any integer, 2 is
@ -148,6 +149,60 @@ func ExtractStats(x any) []Stat {
typ := v.Type()
var ret []Stat
known := make(map[string]bool)
for _, vv := range []reflect.Value{v, v.Addr()} {
xt := vv.Type()
lim := xt.NumMethod()
for i := 0; i < lim; i++ {
m := xt.Method(i)
if !m.IsExported() {
continue
}
tm := m.Type
if tm.NumIn() != 1 {
// 1 arg -- receiver
continue
}
if tm.NumOut() != 1 {
continue
}
nameParts := explode(m.Name)
if len(nameParts) < 2 {
continue
}
isHidden := false
if nameParts[0] == "Hidden" {
isHidden = true
nameParts = nameParts[1:]
}
if nameParts[0] != "Stat" {
continue
}
n := strings.Join(nameParts[1:], " ")
if n == "" {
continue
}
if known[n] {
continue
}
known[n] = true
val := vv.Method(i).Call([]reflect.Value{})
if len(val) != 1 {
// This shouldn't happen - we already checked Out. Weird.
continue
}
if !val[0].CanInterface() {
continue
}
ret = append(ret, &StatLiteral{
Name: n,
Value: fmt.Sprint(val[0].Interface()),
IsVisible: !isHidden,
})
}
}
fields := reflect.VisibleFields(typ)
for _, sf := range fields {
if !sf.IsExported() {
@ -157,7 +212,12 @@ func ExtractStats(x any) []Stat {
if !f.CanInterface() {
continue
}
if s, ok := f.Interface().(Stat); ok {
iface := f.Interface()
if s, ok := iface.(Stat); ok {
if known[s.StatName()] {
continue
}
known[s.StatName()] = true
ret = append(ret, s)
continue
}
@ -181,14 +241,19 @@ func ExtractStats(x any) []Stat {
t = t[5:]
n, _ := strconv.Atoi(t)
fs := fmt.Sprintf("%%.%df", n)
val = fmt.Sprintf(fs, f.Interface())
val = fmt.Sprintf(fs, iface)
} else if isStat {
val = fmt.Sprint(f.Interface())
val = fmt.Sprint(iface)
} else {
continue // not identifiably a stat
}
nm := strings.Join(explode(sf.Name), " ")
if known[nm] {
continue
}
known[nm] = true
ret = append(ret, &StatLiteral{
Name: strings.Join(explode(sf.Name), " "),
Name: nm,
Value: val,
IsVisible: !isHidden,
})
@ -197,50 +262,6 @@ func ExtractStats(x any) []Stat {
// Else, not a stat.
}
lim := typ.NumMethod()
for i := 0; i < lim; i++ {
m := typ.Method(i)
if !m.IsExported() {
continue
}
tm := m.Type
if tm.NumIn() != 1 {
// 1 arg -- receiver
continue
}
if tm.NumOut() != 1 {
continue
}
nameParts := explode(m.Name)
if len(nameParts) < 2 {
continue
}
isHidden := false
if nameParts[0] == "Hidden" {
isHidden = true
nameParts = nameParts[1:]
}
if nameParts[0] != "Stat" {
continue
}
n := strings.Join(nameParts[1:], ": ")
if n == "" {
continue
}
val := v.Method(i).Call([]reflect.Value{})
if len(val) != 1 {
// This shouldn't happen - we already checked Out. Weird.
continue
}
if !val[0].CanInterface() {
continue
}
ret = append(ret, &StatLiteral{
Name: n,
Value: fmt.Sprint(val[0].Interface()),
IsVisible: !isHidden,
})
}
return ret
}

View File

@ -15,6 +15,7 @@ type SmokeTestCollection struct {
Things int `cardsim:"stat"`
MoreThings int `cardsim:"hidden"`
FloatyThings float64 `cardsim:"round1"`
Label string `cardsim:"stat"`
}
func (c *SmokeTestCollection) Average() float64 {

View File

@ -28,6 +28,10 @@ func main() {
Name: "Flavor",
Value: "Lemon",
},
Things: 5,
MoreThings: 9,
FloatyThings: 123.456,
Label: "whee",
},
)
p.Name = "Dave"