Skip to content

Instantly share code, notes, and snippets.

@njones
Last active September 11, 2024 20:57
Show Gist options
  • Save njones/3cba61562e696f6510f4f089b50ac0e7 to your computer and use it in GitHub Desktop.
Save njones/3cba61562e696f6510f4f089b50ac0e7 to your computer and use it in GitHub Desktop.
Code that acts like a HTTP Router for a slice of 'any' arguments ([]any) to methods on a struct that have the expected input argument number types.
package main
import (
"fmt"
"math"
"math/bits"
"reflect"
)
/*
MIT No Attribution
Copyright 2024 Nika Jones
Permission is hereby granted, free of charge, to any person obtaining a copy of this
software and associated documentation files (the "Software"), to deal in the Software
without restriction, including without limitation the rights to use, copy, modify,
merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
const (
maxArgs = 5
sliceTypeID = 1
mapTypeID = 2
arrayTypeID = 3
nibble = 4 // 1/2 a byte or 4 bits
byteSize = 8 // the size of a byte
paramBitsize = 12 // the size of the param bitmap
unusedBitmaskBits = 4 // the 4 bits at the start of the bitmask uint64
highNibbleStartPosition = 60
lowNibbleStartPosition = 56
totalParamsBitsizeMinusOne = ((maxArgs - 1) * paramBitsize)
)
const (
rString uint16 = 1 << iota
rFloat
rInt
rBool
rSlice
rMap
rArray
_ // placeholder for the Any flag
rMapValueKey
rInterface
rStruct
rInvalid
HardAny = iota + 5000
SoftAny
)
var bitmapMap = map[uint]uint16{
uint(reflect.String): rString, // 0x1 0b0001
uint(reflect.Float64): rFloat, // 0x2 0b0010
uint(reflect.Int): rInt, // 0x4 0b0100
uint(reflect.Bool): rBool, // 0x8 0b1000
uint(reflect.Slice): rSlice, // 0x1 0b0001
uint(reflect.Map): rMap, // 0x2 0b0010
uint(reflect.Array): rArray, // 0x4 0b0100
// the Any flag rAny, // 0x8 0b1000
// the map vector rMapVector, // 0x1 0b0001
uint(reflect.Interface): rInterface, // 0x2 0b0010
uint(reflect.Struct): rStruct, // 0x4 0b0100
uint(reflect.Invalid): rInvalid, // 0x8 0b1000
// wider masks
HardAny: 0x6FF,
SoftAny: 0x00F,
}
func GenerateBitmapFromArgs(args []any) uint64 {
bitmaps := make([]uint16, len(args))
for i, value := range args {
val := reflect.ValueOf(value)
// Getting the underlying type
// based on the indirect function from the Go stdlib: 'text/template/exec.go'.
for ; val.Kind() == reflect.Ptr || val.Kind() == reflect.Interface; val = val.Elem() {
if val.IsNil() { // then we use the interface{} type
val = reflect.New(reflect.TypeFor[any]())
break
}
if val.Kind() == reflect.Interface && val.NumMethod() > 0 {
break
}
}
if !val.IsValid() {
val = reflect.New(reflect.TypeFor[any]())
}
tvpe := reflect.TypeOf(val.Interface())
kind, _ := normalizeKind(tvpe.Kind())
if bitflag, ok := bitmapMap[uint(kind)]; ok {
bitmaps[i] = bitflag
switch tvpe.Kind() {
case reflect.Map:
keyKind, _ := normalizeKind(tvpe.Key().Kind())
elemKind, _ := normalizeKind(tvpe.Elem().Kind())
k, v := bitmapMap[uint(keyKind)], bitmapMap[uint(elemKind)]
bitmaps[i] |= k | v
if k > v {
bitmaps[i] |= rMapValueKey
}
case reflect.Array, reflect.Slice:
elemKind, _ := normalizeKind(tvpe.Elem().Kind())
bitmaps[i] |= bitmapMap[uint(elemKind)]
}
}
}
var output uint64
for i, v := range bitmaps {
output |= uint64(v) << (totalParamsBitsizeMinusOne - (paramBitsize * i))
}
return output
}
func GenerateBitmaskFromParams(params []reflect.Type) uint64 {
var bitmasks = make([]uint16, len(params))
for i, param := range params {
kind, _ := normalizeKind(param.Kind())
if bitflags, ok := bitmapMap[uint(kind)]; ok {
bitmasks[i] = bitflags
switch param.Kind() {
case reflect.Interface:
bitmasks[i] |= bitmapMap[uint(HardAny)]
case reflect.Map:
keyKind, _ := normalizeKind(param.Key().Kind())
elemKind, _ := normalizeKind(param.Elem().Kind())
if keyKind == reflect.Interface && elemKind == reflect.Interface {
bitmasks[i] |= bitmapMap[SoftAny]
continue
}
k, v := bitmapMap[uint(keyKind)], bitmapMap[uint(elemKind)]
bitmasks[i] |= k | v
if k > v {
bitmasks[i] |= rMapValueKey
}
case reflect.Array, reflect.Slice:
elemKind, _ := normalizeKind(param.Elem().Kind())
if elemKind == reflect.Interface {
bitmasks[i] |= bitmapMap[SoftAny]
continue
}
bitmasks[i] |= bitmapMap[uint(elemKind)]
}
}
}
var output uint64
for i, v := range bitmasks {
output |= uint64(v) << (totalParamsBitsizeMinusOne - (paramBitsize * i))
}
return output
}
func FindBestBitMatch(bitmap uint64, bitmasks []uint64) uint64 {
var argsCnt int
for i := range maxArgs {
if zeroUpper(uint16(bitmap>>(totalParamsBitsizeMinusOne-(paramBitsize*i))), nibble) > 0 {
argsCnt++
}
}
var bestMaskRank int = math.MaxInt
var bestBitMatch uint64 = math.MaxUint64
var bestSoftAnyParam uint64
for index, bitmask := range bitmasks {
var softAnyParamForMask uint64
var i, paramMatchCnt int
for i = range argsCnt {
var (
argMap = zeroUpper(uint16(bitmap>>(totalParamsBitsizeMinusOne-(paramBitsize*i))), nibble)
paramMask = zeroUpper(uint16(bitmask>>(totalParamsBitsizeMinusOne-(paramBitsize*i))), nibble)
)
if argMap&paramMask == 0 {
goto Next
}
var (
collectionTypeArgFlag = zeroLower(uint8(argMap), nibble)
collectionTypeParamFlag = zeroLower(uint8(paramMask), nibble)
paramAnyFlag = zeroUpper(uint8(paramMask), nibble)
)
// (argMap == paramMask) means the match is exact
// (paramMask > argMap) means the match is wider than the arg
if argMap == paramMask || paramMask > argMap {
if paramAnyFlag == 0xF && // check if the lowest bit is 0xF
uint8(paramMask) != 0xFF { // make sure not 0xFF (a 'hard' any)
// make sure slice/map or array if required
if collectionTypeArgFlag != 0 &&
collectionTypeArgFlag&collectionTypeParamFlag == 0 {
goto Next
}
var softAnyParamCnt = uint8(softAnyParamForMask) // get the current param count
switch uint8(argMap) >> nibble {
case 0b001:
softAnyParamForMask |= sliceTypeID<<highNibbleStartPosition - u64(softAnyParamCnt*byteSize)
case 0b010:
softAnyParamForMask |= mapTypeID<<highNibbleStartPosition - u64(softAnyParamCnt*byteSize)
case 0b100:
softAnyParamForMask |= arrayTypeID<<highNibbleStartPosition - u64(softAnyParamCnt*byteSize)
}
softAnyParamForMask |= u64(i)<<lowNibbleStartPosition - u64(softAnyParamCnt*byteSize)
zeroLower(softAnyParamForMask, nibble) // clear the lowest nibble
softAnyParamForMask |= u64(softAnyParamCnt+1) << 0 // set with the current param count
}
paramMatchCnt++
}
}
{
rest := bitmask << (((i + 1) * paramBitsize) + unusedBitmaskBits)
if bits.OnesCount64(rest) > 0 {
continue
}
}
if paramMatchCnt == argsCnt {
maskRank := bits.OnesCount64(bitmask)
if bestMaskRank = min(bestMaskRank, maskRank); bestMaskRank == maskRank {
bestBitMatch = uint64(index)
bestSoftAnyParam = softAnyParamForMask
}
}
Next:
}
if bestSoftAnyParam > 0 {
bestSoftAnyParam = (bestSoftAnyParam >> u64(64-(uint8(bestSoftAnyParam)*byteSize)))
bestBitMatch |= bestSoftAnyParam << byteSize
}
return bestBitMatch
}
func Parse[T any](args []any, mux *T) T {
if reflect.TypeOf(mux).Kind() != reflect.Pointer {
panic("must use a pointer receiver")
}
if reflect.TypeOf(mux).Elem().Kind() != reflect.Struct {
panic("must use a pointer to a struct")
}
if len(args) > maxArgs {
panic("must use 5 or less args with the dispatch system")
}
args = normalizeArgs(args)
var muxPtr = reflect.TypeOf(mux)
var muxTyp = muxPtr.Elem()
var muxVal = reflect.ValueOf(mux).Elem()
var callbackFnIdx int
var callbackFnTyp reflect.Type
for i := 0; i < muxTyp.NumField(); i++ {
fieldTyp := muxTyp.Field(i).Type
fieldVal := muxVal.Field(i)
if fieldTyp.Kind() == reflect.Func && fieldVal.CanSet() {
callbackFnIdx = i
callbackFnTyp = fieldTyp
break
}
}
var returnSig []reflect.Type
var zeroVals = make([]reflect.Value, callbackFnTyp.NumOut())
for i := 0; i < callbackFnTyp.NumOut(); i++ {
returnSig = append(returnSig, callbackFnTyp.Out(i))
zeroVals[i] = reflect.Zero(callbackFnTyp.Out(i))
}
if returnSig[len(returnSig)-1].Kind() != reflect.TypeFor[error]().Kind() {
panic("must return an error value")
}
var methodIdxs []int
for i := 0; i < muxPtr.NumMethod(); i++ {
methodTyp := muxPtr.Method(i).Type
if methodTyp.NumOut() != len(returnSig) {
continue // return signatures aren't the same length so skip
}
for n := 0; n < methodTyp.NumOut(); n++ {
if returnSig[n] != methodTyp.Out(n) {
goto Next // one of the return types don't match so skip
}
}
methodIdxs = append(methodIdxs, i)
Next:
}
var argVals = make([]reflect.Value, len(args))
for i, value := range args {
val := reflect.ValueOf(value)
// Getting the underlying type
// based on the indirect function from the Go stdlib: 'text/template/exec.go'.
for ; val.Kind() == reflect.Ptr || val.Kind() == reflect.Interface; val = val.Elem() {
if val.IsNil() {
val = reflect.New(reflect.TypeFor[any]())
break
}
if val.Kind() == reflect.Interface && val.NumMethod() > 0 {
break
}
}
if !val.IsValid() {
val = reflect.New(reflect.TypeFor[any]())
}
argVals[i] = val
}
var muxBitmasks = make([]uint64, len(methodIdxs))
for i, methodIdx := range methodIdxs {
var params []reflect.Type
methodTyp := muxPtr.Method(methodIdx).Type
// n == 1 to remove first parameter type (the struct pointer)
// see: https://go.dev/ref/spec#Method_expressions
for n := 1; n < methodTyp.NumIn(); n++ {
params = append(params, methodTyp.In(n))
}
muxBitmasks[i] = GenerateBitmaskFromParams(params)
}
var callbackFn reflect.Value
var argBitMaps = GenerateBitmapFromArgs(args)
if methodIdx := FindBestBitMatch(argBitMaps, muxBitmasks); methodIdx != math.MaxUint64 {
callbackFn = reflect.MakeFunc(callbackFnTyp, func(args []reflect.Value) (results []reflect.Value) {
idx := int(uint8(methodIdx))
shiftKindPos, shiftIdxPos := (byteSize + nibble), byteSize
Convert:
softAnykind, softAnyArgIdx := zeroUpper(uint8(methodIdx>>shiftKindPos), 4), zeroUpper(uint8(methodIdx>>shiftIdxPos), 4)
if softAnykind > 0 {
// TODO(njones): make sure we don't already have an interface...
switch softAnykind {
case sliceTypeID:
var anySlice = make([]any, argVals[softAnyArgIdx].Len())
for i := 0; i < argVals[softAnyArgIdx].Len(); i++ {
anySlice[i] = argVals[softAnyArgIdx].Index(i).Interface()
}
argVals[softAnyArgIdx] = reflect.ValueOf(anySlice)
case mapTypeID:
var anyMap = make(map[any]any)
for _, k := range argVals[softAnyArgIdx].MapKeys() {
v := argVals[softAnyArgIdx].MapIndex(k)
anyMap[k.Interface()] = v.Interface()
}
argVals[softAnyArgIdx] = reflect.ValueOf(anyMap)
case arrayTypeID:
paramArrayLen := reflect.ValueOf(mux).Method(methodIdxs[idx]).Type().In(int(softAnyArgIdx)).Len()
argArrayLen := argVals[softAnyArgIdx].Len()
var anyArray = reflect.New(reflect.ArrayOf(paramArrayLen, reflect.TypeFor[any]())).Elem()
for i := 0; i < min(argArrayLen, paramArrayLen); i++ {
anyArray.Index(i).Set(argVals[softAnyArgIdx].Index(i))
}
argVals[softAnyArgIdx] = anyArray
}
shiftKindPos += byteSize
shiftIdxPos += byteSize
goto Convert
}
return reflect.ValueOf(mux).Method(methodIdxs[idx]).Call(argVals)
})
} else {
callbackFn = reflect.MakeFunc(callbackFnTyp, func(args []reflect.Value) (results []reflect.Value) {
zeroVals[len(zeroVals)-1] = reflect.ValueOf(fmt.Errorf("no matching methods found"))
return zeroVals
})
}
muxVal.Field(callbackFnIdx).Set(callbackFn)
return muxVal.Interface().(T)
}
func normalizeType(tvpe reflect.Type) (reflect.Type, bool) {
var ok bool
if value, ok := normalizeKind(tvpe.Kind()); ok {
switch value {
case reflect.Int:
return reflect.TypeFor[int](), true
case reflect.Float64:
return reflect.TypeFor[float64](), true
}
}
return tvpe, ok
}
func normalizeKind(kind reflect.Kind) (reflect.Kind, bool) {
var ok bool
switch kind {
case reflect.Int8, reflect.Int16,
reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16,
reflect.Uint32, reflect.Uint64:
kind, ok = reflect.Int, true
case reflect.Float32:
kind, ok = reflect.Float64, true
}
return kind, ok
}
func normalizeAny[T int | float64](value any) (T, bool) {
var o T
switch val := value.(type) {
case int8, int16, int32, int64:
if _, ok := any(o).(int); ok {
return T(int(reflect.ValueOf(value).Int())), true
}
case uint, uint8, uint16, uint32, uint64:
if _, ok := any(o).(int); ok {
return T(int(reflect.ValueOf(value).Uint())), true
}
case float32:
if _, ok := any(o).(float64); ok {
return T(float64(val)), true
}
}
return o, false
}
func normalizeValue(value reflect.Value) (reflect.Value, bool) {
var ok bool
switch val, found := normalizeKind(value.Type().Kind()); val {
case reflect.Int:
if found {
if v, use := normalizeAny[int](value.Interface()); use {
value, ok = reflect.ValueOf(v), true
}
}
case reflect.Float64:
if found {
if v, use := normalizeAny[float64](value.Interface()); use {
value, ok = reflect.ValueOf(v), true
}
}
}
return value, ok
}
func normalizeArgs(args []any) []any {
for i, value := range args {
switch value.(type) {
case int8, int16, int32, int64,
uint, uint8, uint16, uint32, uint64:
if val, ok := normalizeAny[int](value); ok {
args[i] = val
continue
}
case float32:
if val, ok := normalizeAny[float64](value); ok {
args[i] = val
continue
}
}
val := reflect.ValueOf(value)
typ := val.Type()
switch typ.Kind() {
// TODO(njones): Make it work recursive with array/slice keys
case reflect.Map:
key, changeKey := normalizeType(typ.Key())
elem, changeElem := normalizeType(typ.Elem())
if changeKey || changeElem {
_map := reflect.MakeMapWithSize(reflect.MapOf(key, elem), 0)
for _, _key := range val.MapKeys() {
_elem := val.MapIndex(_key)
if changeKey {
_key, _ = normalizeValue(_key)
}
if changeElem {
_elem, _ = normalizeValue(_elem)
}
_map.SetMapIndex(_key, _elem)
}
args[i] = _map.Interface()
}
case reflect.Array, reflect.Slice:
if elem, changeElem := normalizeType(typ.Elem()); changeElem {
_slice := reflect.MakeSlice(reflect.SliceOf(elem), val.Len(), val.Len())
for i := 0; i < val.Len(); i++ {
v, _ := normalizeValue(val.Index(i))
_elem := _slice.Index(i)
_elem.Set(v)
}
args[i] = _slice.Interface()
}
}
}
return args
}
func zeroLower[T uint8 | uint16 | uint32 | uint64](v T, num int) T {
v &^= (1 << num) - 1
return v
}
func zeroUpper[T uint8 | uint16 | uint32 | uint64](v T, num int) T {
v &^= (1<<num - 1) << (reflect.TypeOf(v).Bits() - num)
return v
}
func u64[T uint8 | uint16 | uint32 | uint | int](v T) uint64 { return uint64(v) }
package main
import (
"fmt"
"log"
"reflect"
"strings"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
)
/*
MIT No Attribution
Copyright 2024 Nika Jones
Permission is hereby granted, free of charge, to any person obtaining a copy of this
software and associated documentation files (the "Software"), to deal in the Software
without restriction, including without limitation the rights to use, copy, modify,
merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
type argsFn[T any, S any | *simple | []uint64, X string | uint64] func(T, S, X, error) func(*testing.T)
func (fn argsFn[T, S, X]) f1(t struct {
arg1 T
expected X
}) func(*testing.T) {
var s S
return fn(t.arg1, s, t.expected, nil)
}
func (fn argsFn[T, S, X]) f2(t struct {
arg1 T
arg2 S
expected X
err error
}) func(*testing.T) {
return fn(t.arg1, t.arg2, t.expected, t.err)
}
// A diagram of how things break down for checking and making tests.
//
// 0x01 = 00000001 | 0x02 00000010 | 0x04 00000100 | 0x08 00001000
// 0x10 = 00010000 | 0x20 00100000 | 0x40 01000000 | 0x80 10000000
//
// 0000_000000000000_000000000000_000000000000_000000000000_000000000000
// xxxx param1 param2 param3 param4 param5
//
// Flags of the different sections of a parameter
//
// bits 3 2 1 soft filter
// 0001 mapKV slice string uint(1) slice
// 0010 interface{} map float64 uint(2) map
// 0100 struct array int uint(3) array
// 1000 invalid isAny bool
//
// An example bitmap for a int slice = 0b0000_0001_0100 (0x014)
// sections: ^3 ^2 ^1
func TestGenerateBitmapFromArgs(t *testing.T) {
type input = struct {
arg1 []any
expected uint64
}
test := argsFn[[]any, any, uint64](func(args []any, _ any, expected uint64, _ error) func(t *testing.T) {
return func(t *testing.T) {
got := GenerateBitmapFromArgs(args)
diff := cmp.Diff(expected, got)
if diff != "" {
t.Error(diff)
t.Errorf("\n0x%012b\n0x%012b", expected, got)
}
}
})
t.Run("one arg", test.f1(input{
arg1: []any{1},
expected: 0x0004000000000000, // 0b0000_000000000100
}))
t.Run("two args", test.f1(input{
arg1: []any{1, "two"},
expected: 0x0004001000000000, // 0b0000_000000000100_000000000001
}))
t.Run("map", test.f1(input{
arg1: []any{map[string]int{"one": 2}},
expected: 0x0025000000000000, // 0b0000_0000001001001
}))
t.Run("soft slice", test.f1(input{
arg1: []any{[]any{1.2, "three", 4}},
expected: 0x0210000000000000, // 0b0000_001000010000
}))
t.Run("hard any - doesn't exist", test.f1(input{
arg1: []any{any(0), any("")},
expected: 0x0004001000000000, // 0b0000_000000000100_000000000001
}))
}
func TestGenerateBitmaskFromParams(t *testing.T) {
type input = struct {
arg1 []reflect.Type
expected uint64
}
test := argsFn[[]reflect.Type, any, uint64](func(params []reflect.Type, _ any, expected uint64, _ error) func(t *testing.T) {
return func(t *testing.T) {
got := GenerateBitmaskFromParams(params)
diff := cmp.Diff(expected, got)
if diff != "" {
t.Error(diff)
t.Errorf("\n0x%016x\n0x%016x", expected, got)
}
}
})
t.Run("one param", test.f1(input{
arg1: []reflect.Type{reflect.TypeFor[int]()},
expected: 0x0004000000000000, // 0b0000_000000000100
}))
t.Run("two params", test.f1(input{
arg1: []reflect.Type{reflect.TypeFor[int](), reflect.TypeFor[string]()},
expected: 0x0004001000000000, // 0b0000_000000000100_000000000001
}))
t.Run("map", test.f1(input{
arg1: []reflect.Type{reflect.TypeFor[map[int]string]()},
expected: 0x0125000000000000, // 0b0000_000100100101
}))
t.Run("soft slice", test.f1(input{
arg1: []reflect.Type{reflect.TypeFor[[]any]()},
expected: 0x001f000000000000, // 0b0000_000000011111
}))
t.Run("hard any", test.f1(input{
arg1: []reflect.Type{reflect.TypeFor[any]()},
expected: 0x06ff000000000000, // 0b0000_001011111111
}))
}
func TestFindBestBitMatch(t *testing.T) {
type input = struct {
arg1 uint64
arg2 []uint64
expected uint64
err error
}
test := argsFn[uint64, []uint64, uint64](func(_map uint64, _masks []uint64, expected uint64, _ error) func(t *testing.T) {
return func(t *testing.T) {
got := FindBestBitMatch(_map, _masks)
diff := cmp.Diff(expected, got)
if diff != "" {
t.Error(diff)
t.Errorf("\n%016x\n%016x", expected, got)
}
}
})
t.Run("match 1", test.f2(input{
arg1: 0x0004000000000000,
arg2: []uint64{
0x001f000000000000,
0x0004000000000000,
0x02ff000000000000,
},
expected: 0x1,
}))
t.Run("match two", test.f2(input{
arg1: 0x0004001000000000,
arg2: []uint64{
0x001f000000000000,
0x0004000000000000,
0x0004001000000000,
0x02ff000000000000,
},
expected: 0x2,
}))
t.Run("match soft slice", test.f2(input{
arg1: 0x0014000000000000,
arg2: []uint64{
0x0004000000000000,
0x0004001000000000,
0x02ff000000000000,
0x001f000000000000,
},
expected: 0x1003,
}))
t.Run("match hard any", test.f2(input{
arg1: 0x0_008_000_000_000_000,
arg2: []uint64{
0x0004000000000000,
0x0004001000000000,
0x02ff000000000000,
0x001f000000000000,
},
expected: 0x3,
}))
t.Run("match none", test.f2(input{
arg1: 0x0004002001001000,
arg2: []uint64{
0x0004000000000000,
0x0004001000000000,
0x02ff000000000000,
0x001f000000000000,
},
expected: 0xFFFFFFFFFFFFFFFF,
}))
}
func TestParse(t *testing.T) {
type example struct{ a string }
type test = struct {
arg1 []any
arg2 *simple
expected string
err error
}
parse := argsFn[[]any, *simple, string](func(args []any, dispatch *simple, expected string, expErr error) func(t *testing.T) {
return func(t *testing.T) {
got, err := Parse(args, dispatch).Output()
diff := cmp.Diff(expected, got)
if diff != "" {
t.Error(diff)
}
if aerr := cmp.Diff(err, expErr, cmpopts.EquateErrors()); aerr != "" {
if (err != nil && expErr != nil) && err.Error() != expErr.Error() { // check if sential is correct
t.Error(aerr)
} else {
t.Log("sentinel errors match")
}
}
}
})
t.Run("simple1", parse.f2(test{
arg1: []any{"Hello World"},
arg2: &simple{},
expected: "< Hello World >",
}))
t.Run("simple2", parse.f2(test{
arg1: []any{[]string{"Hello", "World"}, "|"},
arg2: &simple{},
expected: "Hello|World",
}))
t.Run("soft", parse.f2(test{
arg1: []any{[]int{1, 2, 3}},
arg2: &simple{},
expected: "< 6 >",
}))
t.Run("hard", parse.f2(test{
arg1: []any{2.5, ":: "},
arg2: &simple{},
expected: ":: <float64 Value>",
}))
t.Run("soft array", parse.f2(test{
arg1: []any{[2]int{0, 1}}, // note Accept3a takes a [20]any array
arg2: &simple{},
expected: "< 1 >",
}))
t.Run("normalize slice", parse.f2(test{
arg1: []any{[]uint32{0, 1, 2, 3, 4}},
arg2: &simple{},
expected: "< 10 >",
}))
t.Run("normalize map", parse.f2(test{
arg1: []any{map[string]uint32{"a": 0, "b": 1, "c": 2, "d": 3, "e": 4}, "^^ "},
arg2: &simple{},
expected: "^^ <map[string]int Value>",
}))
t.Run("struct", parse.f2(test{
arg1: []any{&example{}, "$$ "},
arg2: &simple{},
expected: "$$ <main.example Value>",
}))
t.Run("error", parse.f2(test{
arg1: []any{1, 2, 3, 4, 5},
arg2: &simple{},
err: fmt.Errorf("no matching methods found"),
}))
}
type simple struct{ Output func() (string, error) }
func (s *simple) Accept1(a string) (string, error) { return fmt.Sprintf("< %s >", a), nil }
func (s *simple) Accept2(a []string, b string) (string, error) { return strings.Join(a, b), nil }
func (s *simple) Accept3a(a [20]any) (string, error) { return s.Accept3(a[:]) }
func (s *simple) Accept3(a []any) (string, error) {
var cnt int
for _, v := range a {
if val, ok := v.(int); ok {
cnt += val
}
}
return fmt.Sprintf("< %d >", cnt), nil
}
func (s *simple) Accept4(a any, b string) (string, error) {
log.Printf("%T", a)
return b + reflect.ValueOf(a).String(), nil
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment