gob/convert/convert.go

767 lines
15 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package convert
import (
"fmt"
"sort"
"strconv"
"unicode/utf8"
. "gitea.zaclys.com/bvaudour/gob/option"
)
type BoolType interface {
~bool
}
type CharType interface {
~byte | ~rune
}
type StringType interface {
~string
}
type IntType interface {
~int | ~int8 | ~int16 | ~int32 | ~int64
}
type UintType interface {
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64
}
type FloatType interface {
~float32 | ~float64
}
type ComplexType interface {
~complex64 | ~complex128
}
type IntegerType interface {
IntType | UintType
}
type RealType interface {
IntegerType | FloatType
}
type NumberType interface {
RealType | ComplexType
}
type convFunc = func(Value, Value) bool
func isConvertible(t1, t2 Type) bool {
return t1.AssignableTo(t2) || t1.EquivalentTo(t2)
}
func getConvFunc(t Type) (f Option[convFunc]) {
switch {
case t.Is(Bool):
f = Some(fromBool)
case t.Is(Char):
f = Some(fromChar)
case t.Is(Int):
f = Some(fromInt)
case t.Is(Uint):
f = Some(fromUint)
case t.Is(Float):
f = Some(fromFloat)
case t.Is(Complex):
f = Some(fromComplex)
case t.Is(String):
f = Some(fromString)
case t.Is(Slice.Or(Array)):
f = Some(fromSlice)
case t.IsSet():
f = Some(fromSet)
case t.Is(Map):
f = Some(fromMap)
case t.Is(Struct):
f = Some(fromStruct)
}
return
}
func safeConv(src, dest Value) bool {
ts, td := src.Type(), dest.Type()
if ts.AssignableTo(td) {
dest.Set(src)
return true
} else if ts.EquivalentTo(td) {
dest.Set(src.Convert(td))
return true
}
return false
}
func unsafeConv(src, dest Value) bool {
f, ok := getConvFunc(src.Type()).Get()
return ok && f(src, dest)
}
func conv(src, dest Value, safe ...bool) bool {
if safeConv(src, dest) {
return true
} else if len(safe) == 0 || !safe[0] {
return unsafeConv(src, dest)
} else if src.Is(Slice.Or(Array)) && dest.Is(Slice.Or(Array)) {
return safeConvSlice(src, dest)
} else if src.Is(Map) && dest.Is(Map) {
return safeConvMap(src, dest)
}
return false
}
func safeConvSlice(src, dest Value) (ok bool) {
l, t := src.Len(), dest.Type()
isArray := t.Is(Array)
var v Value
if isArray {
if t.Len() != l {
return
}
v = SliceOf(SliceTypeOf(t.Elem()), l, src.Cap())
} else {
v = SliceOf(t, l, src.Cap())
}
ok = true
for i := 0; i < l; i++ {
e1, e2 := src.Index(i), v.Index(i)
if ok = Convert(e1.Interface(), e2.Pointer().Interface(), true); !ok {
return
}
}
if isArray {
v = v.Convert(PointerTo(t)).Elem()
}
dest.Set(v)
return
}
func safeConvMap(src, dest Value) (ok bool) {
t := dest.Type()
tk, tv := t.Key(), t.Elem()
v := MapOf(t)
keys := src.Keys()
ok = true
for _, k1 := range keys {
k2 := PointerOf(tk)
if ok = Convert(k1.Interface(), k2.Interface(), true); !ok {
return
}
e1, e2 := src.MapIndex(k1), PointerOf(tv)
if ok = Convert(e1.Interface(), e2.Interface(), true); !ok {
return
}
v.SetMapIndex(k2.Elem(), e2.Elem())
}
dest.Set(v)
return
}
func toSlice(src, dest Value) (ok bool) {
t := dest.Type()
r := AddressableOf(t.Elem())
if ok = conv(src, r); ok {
sl := SliceOf(t, 1, 1)
sl.Index(0).Set(r)
dest.Set(sl)
}
return
}
func toArray(src, dest Value) (ok bool) {
t := dest.Type()
tsl := SliceTypeOf(t.Elem())
sl := AddressableOf(tsl)
if ok = toSlice(src, sl); ok {
dest.Set(sl.Convert(t))
}
return
}
func toSet(src, dest Value) (ok bool) {
t := dest.Type()
r := AddressableOf(t.Key())
if ok = conv(src, r); ok {
m := MapOf(t)
m.AddToSet(r)
dest.Set(m)
}
return
}
func fromBool(src, dest Value) (ok bool) {
e := src.Bool()
var r any
switch {
case dest.Is(Bool):
r, ok = e, true
case dest.Is(Number):
r, ok = 0, true
if e {
r = 1
}
case dest.Is(String):
r, ok = strconv.FormatBool(e), true
case dest.Is(Slice):
return toSlice(src, dest)
case dest.Is(Array):
return toArray(src, dest)
case dest.IsSet():
return toSet(src, dest)
}
if ok {
v := ValueOf(r).Convert(dest.Type())
dest.Set(v)
}
return
}
func fromChar(src, dest Value) (ok bool) {
e := src.Int()
var r any
switch {
case dest.Is(Bool):
r, ok = e != 0, true
case dest.Is(Char):
r, ok = e, true
case dest.Is(Number):
r, ok = e-int64('0'), true
case dest.Is(String):
r, ok = fmt.Sprintf("%c", e), true
case dest.Is(Slice):
return toSlice(src, dest)
case dest.Is(Array):
return toArray(src, dest)
case dest.IsSet():
return toSet(src, dest)
}
if ok {
v := ValueOf(r).Convert(dest.Type())
dest.Set(v)
}
return
}
func fromString(src, dest Value) (ok bool) {
e := src.String()
var r any
var err error
switch {
case dest.Is(Bool):
r, err = strconv.ParseBool(e)
ok = err == nil
case dest.Is(Char):
if ok = utf8.RuneCountInString(e) == 1; ok {
r, _ = utf8.DecodeRuneInString(e)
}
case dest.Is(Int):
r, err = strconv.ParseInt(e, 0, 64)
ok = err == nil
case dest.Is(Uint):
r, err = strconv.ParseUint(e, 0, 64)
ok = err == nil
case dest.Is(Float):
r, err = strconv.ParseFloat(e, 64)
ok = err == nil
case dest.Is(Complex):
r, err = strconv.ParseComplex(e, 128)
ok = err == nil
case dest.Is(Slice):
return toSlice(src, dest)
case dest.Is(Array):
return toArray(src, dest)
case dest.IsSet():
return toSet(src, dest)
}
if ok {
v := ValueOf(r).Convert(dest.Type())
dest.Set(v)
}
return
}
func fromNumber[T RealType](src, dest Value, e T, f func(T) any) (ok bool) {
var r any
switch {
case dest.Is(Bool):
r, ok = e != 0, true
case dest.Is(Char):
r, ok = rune(int64(e))+'0', true
case dest.Is(Number):
r, ok = e, true
case dest.Is(String):
r, ok = f(e), true
case dest.Is(Slice):
return toSlice(src, dest)
case dest.Is(Array):
return toArray(src, dest)
case dest.IsSet():
return toSet(src, dest)
}
if ok {
v := ValueOf(r).Convert(dest.Type())
dest.Set(v)
}
return
}
func fromInt(src, dest Value) (ok bool) {
e := src.Int()
return fromNumber(
src,
dest,
e,
func(n int64) any { return strconv.FormatInt(n, 10) },
)
}
func fromUint(src, dest Value) (ok bool) {
e := src.Uint()
return fromNumber(
src,
dest,
e,
func(n uint64) any { return strconv.FormatUint(n, 10) },
)
}
func fromFloat(src, dest Value) (ok bool) {
e := src.Float()
return fromNumber(
src,
dest,
e,
func(n float64) any { return strconv.FormatFloat(n, 'f', -1, 64) },
)
}
func fromComplex(src, dest Value) (ok bool) {
e := src.Complex()
var r any
switch {
case dest.Is(Bool):
r, ok = e != 0, true
case dest.Is(Char):
r, ok = rune(real(e))+'0', true
case dest.Is(Real):
r, ok = real(e), true
case dest.Is(Complex):
r, ok = e, true
case dest.Is(String):
r, ok = strconv.FormatComplex(e, 'f', -1, 64), true
case dest.Is(Slice):
return toSlice(src, dest)
case dest.Is(Array):
return toArray(src, dest)
case dest.IsSet():
return toSet(src, dest)
}
if ok {
v := ValueOf(r).Convert(dest.Type())
dest.Set(v)
}
return
}
func fromSlice(src, dest Value) (ok bool) {
t, l := dest.Type(), src.Len()
var r Value
switch {
case dest.Is(Slice):
r, ok = SliceOf(t, l, src.Cap()), true
for i := 0; i < l; i++ {
if ok = conv(src.Index(i, true), r.Index(i)); !ok {
return
}
}
case dest.Is(Array):
if ok = l == t.Len(); !ok {
return
}
tsl := SliceTypeOf(t.Elem())
r = SliceOf(tsl, l, src.Cap())
if ok = fromSlice(src, r); !ok {
return
}
r = r.Convert(PointerTo(t)).Elem()
case dest.IsSet():
tk := t.Key()
r = MapOf(t)
for i := 0; i < l; i++ {
e1, e2 := src.Index(i, true), AddressableOf(tk)
if ok = conv(e1, e2); !ok {
return
}
r.AddToSet(e2)
}
case dest.Is(Map):
t := dest.Type()
tk, tv := t.Key(), t.Elem()
r = MapOf(t)
for i := 0; i < l; i++ {
e1, e2 := src.Index(i, true), AddressableOf(tv)
if ok = conv(e1, e2); !ok {
return
}
k := AddressableOf(tk)
if ok = conv(ValueOf(i), k); !ok {
return
}
r.SetMapIndex(k, e2)
}
default:
if ok = l == 1; ok {
return conv(src.Index(0, true), dest)
}
}
if ok {
dest.Set(r)
}
return
}
func fromSet(src, dest Value) (ok bool) {
t, keys := dest.Type(), src.Keys()
var r Value
switch {
case dest.Is(Slice):
r, ok = SliceOf(t, len(keys), len(keys)), true
for i, k := range keys {
if ok = conv(src.MapIndex(k, true), r.Index(i)); !ok {
return
}
}
case dest.Is(Array):
if ok = len(keys) == t.Len(); !ok {
return
}
tsl := SliceTypeOf(t.Elem())
r = SliceOf(tsl, len(keys), len(keys))
if ok = fromSet(src, r); ok {
r = r.Convert(PointerTo(t)).Elem()
}
case dest.IsSet():
tk := t.Key()
r, ok = MapOf(t), true
for _, k := range keys {
e1, e2 := src.MapIndex(k, true), AddressableOf(tk)
if ok = conv(e1, e2); !ok {
return
}
r.AddToSet(e2)
}
default:
if ok = len(keys) == 1; ok {
ok = conv(src.MapIndex(keys[0], true), dest)
}
}
if ok {
dest.Set(r)
}
return
}
func fromMap(src, dest Value) (ok bool) {
t, keys := dest.Type(), src.Keys()
var r Value
switch {
case dest.Is(Slice):
idx := make([]int, len(keys))
ki := make(map[Value]int)
for i, k := range keys {
var e int
if ok = conv(ValueOf(k.Interface()), ValueOf(&e).Elem()); !ok {
return
}
ki[k], idx[i] = e, e
}
sort.Ints(idx)
for i, j := range idx {
if ok = i == j; !ok {
return
}
}
r, ok = SliceOf(t, len(keys), len(keys)), true
for _, k := range keys {
if ok = conv(src.MapIndex(k, true), r.Index(ki[k])); !ok {
return
}
}
case dest.Is(Array):
if ok = t.Len() != len(keys); !ok {
return
}
tsl := SliceTypeOf(t.Elem())
r = AddressableOf(tsl)
if ok = fromMap(src, r); ok {
r = r.Convert(PointerTo(t)).Elem()
}
case dest.Is(Map):
tk, tv := t.Key(), t.Elem()
r, ok = MapOf(t), true
for _, k := range keys {
e1, e2 := src.MapIndex(k, true), AddressableOf(tv)
if ok = conv(e1, e2); !ok {
return
}
kd := AddressableOf(tk)
if ok = conv(ValueOf(k.Interface()), kd); !ok {
return
}
r.SetMapIndex(kd, e2)
}
case dest.Is(Struct):
r, ok = AddressableOf(t), true
for _, k := range keys {
var fs string
if !Convert(k.Interface(), &fs) {
continue
}
if f, exists := t.FieldByName(fs); !exists || f.PkgPath != "" {
continue
}
e1, e2 := src.MapIndex(k, true), r.FieldByName(fs)
if !e1.Is(Ptr) && e2.Is(Ptr) {
e2.Set(PointerOf(e2.TypeElem()))
e2 = e2.Elem()
}
if ok = conv(e1, e2); !ok {
return
}
}
}
if ok {
dest.Set(r)
}
return
}
func fromStruct(src, dest Value) (ok bool) {
ts, t := src.Type(), dest.Type()
fields := ts.Fields()
var r Value
switch {
case dest.Is(Struct):
r, ok = AddressableOf(t), true
for _, f := range fields {
if _, exists := t.FieldByName(f.Name); !exists {
continue
}
e1, e2 := src.FieldByName(f.Name, true), r.FieldByName(f.Name)
if !e1.Is(Ptr) && e2.Is(Ptr) {
e2.Set(PointerOf(e2.TypeElem()))
e2 = e2.Elem()
} else if e1.Is(Ptr) && !e2.Is(Ptr) {
if e1.IsNil() {
e1 = ZeroOf(e1.Type().Elem())
} else {
e1 = e1.Elem()
}
}
if ok = conv(e1, e2); !ok {
return
}
}
case dest.Is(Map):
r, ok = MapOf(t), true
tk, tv := t.Key(), t.Elem()
for _, f := range fields {
e1, e2 := src.FieldByName(f.Name, true), AddressableOf(tv)
if e1.Is(Ptr) && !e2.Is(Ptr) {
if e1.IsNil() {
e1 = ZeroOf(e1.Type().Elem())
} else {
e1 = e1.Elem()
}
}
if ok = conv(e1, e2); !ok {
return
}
k := AddressableOf(tk)
if ok = conv(ValueOf(f.Name), k); !ok {
return
}
r.SetMapIndex(k, e2)
}
}
if ok {
dest.Set(r)
}
return
}
// Convert convertit la valeur du paramètre source vers
// le paramètre destination et retourne true si lopération
// sest effectuée avec succès.
// La destination doit être un pointeur, afin de pouvoir modifier
// sa valeur.
// Si le paramètre optionnel safe est fourni et vaut true,
// la destination nest modifiée que si son type est équivalent
// à celui de la source (par exemple int vers int64, mais
// pas int vers string).
func Convert(src, dest any, safe ...bool) bool {
vs, vd := ValueOf(src), ValueOf(dest)
if !vd.Is(Ptr) || vd.IsNil() {
return false
}
return conv(vs, vd.Elem(), safe...)
}
func setDefault[T any](def ...T) (out T) {
if len(def) > 0 {
out = def[0]
}
return
}
func toSingle[T BoolType | StringType | NumberType](src any, def ...T) (dest T) {
if ok := Convert(src, &dest); !ok {
dest = setDefault(def...)
}
return
}
func ToBool[T BoolType](src any, def ...T) T {
return toSingle(src, def...)
}
func ToChar[T CharType](src any, def ...T) T {
return toSingle(src, def...)
}
func ToInt[T IntType](src any, def ...T) T {
return toSingle(src, def...)
}
func ToUint[T UintType](src any, def ...T) T {
return toSingle(src, def...)
}
func ToInteger[T IntegerType](src any, def ...T) T {
return toSingle(src, def...)
}
func ToFloat[T FloatType](src any, def ...T) T {
return toSingle(src, def...)
}
func ToComplex[T ComplexType](src any, def ...T) T {
return toSingle(src, def...)
}
func ToString[T StringType](src any, def ...T) T {
return toSingle(src, def...)
}
func ToSlice[T any](src any, def ...T) (dest []T) {
if ok := Convert(src, &dest); !ok {
dest = def
}
return
}
func ToMap[K comparable, V any](src any, def ...map[K]V) (dest map[K]V) {
if ok := Convert(src, &dest); !ok {
if len(def) > 0 {
dest = def[0]
} else {
dest = make(map[K]V)
}
}
return
}
func ToSet[K comparable](src any, def ...map[K]struct{}) map[K]struct{} {
return ToMap(src, def...)
}
// SetZero réinitialise la variable pointée
// par le paramètre dentrée à sa valeur initiale
func SetZero(dst any) bool {
vd := ValueOf(dst)
if !vd.Is(Ptr) {
return false
}
z := ZeroOf(vd.TypeElem())
vd.SetElem(z)
return true
}
func clone(v Value, deep ...bool) Value {
t := v.Type()
var c Value
switch {
case v.IsNil():
c = v
case v.Is(Ptr):
if len(deep) == 0 || !deep[0] {
return v
}
vc := clone(v.Elem(), deep...)
c = PointerOf(t.Elem())
c.SetElem(vc)
case v.Is(Slice):
l := v.Len()
c := SliceOf(t, l, v.Cap())
for i := 0; i < l; i++ {
s, d := v.Index(i), c.Index(i)
d.Set(clone(s, deep...))
}
case v.Is(Array):
l := v.Len()
tsl := SliceTypeOf(t.Elem())
c := SliceOf(tsl, l, v.Cap())
for i := 0; i < l; i++ {
s, d := v.Index(i), c.Index(i)
d.Set(clone(s, deep...))
}
c = c.Convert(PointerTo(t)).Elem()
case v.Is(Map):
c = MapOf(t)
for _, k := range v.Keys() {
s := v.MapIndex(k)
c.SetMapIndex(clone(k, deep...), clone(s, deep...))
}
case v.Is(Struct):
c := ValueOf(t)
n := t.NumField()
for i := 0; i < n; i++ {
f := t.Field(i)
if f.PkgPath != "" {
continue
}
s, d := v.Field(i), c.Field(i)
d.Set(clone(s, deep...))
}
default:
c = PointerOf(t)
Convert(v.Interface(), c.Interface())
c = c.Elem()
}
return c
}
// CloneInterface est léquivalent rapide de Clone
// si la valeur de sortie na pas besoin dêtre typée.
func CloneInterface(e any, deep ...bool) any {
c := clone(ValueOf(e), deep...)
return c.Interface()
}
// Clone retourne une copie de lélément fournit en paramètre.
// Si deep est fourni et vaut true, le clonage seffectue récursivement.
func Clone[T any](e T, deep ...bool) T {
c := clone(ValueOf(e), deep...)
if c.IsNil() {
return e
}
var out T
v := ValueOf(&out)
v.SetElem(c)
return v.Interface().(T)
}