Last active
September 11, 2024 20:57
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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¶mMask == 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) } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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