Files
tablizer/vendor/github.com/glycerine/zygomys/zygo/jsonmsgp.go
2024-05-14 12:10:58 +02:00

1161 lines
31 KiB
Go

package zygo
import (
"bytes"
"fmt"
"github.com/shurcooL/go-goon"
"github.com/ugorji/go/codec"
"reflect"
"sort"
"strings"
"time"
"unsafe"
)
type TypeCheckable interface {
TypeCheck() error
}
/*
Conversion map
Go map[string]interface{} <--(1)--> lisp
^ ^ |
| / |
(2) ------------ (4) -----------/ (5)
| / |
V V V
msgpack <--(3)--> go struct, strongly typed
(1) we provide these herein; see jsonmsgp_test.go too.
(a) SexpToGo()
(b) GoToSexp()
(2) provided by ugorji/go/codec; see examples also herein
(a) MsgpackToGo() / JsonToGo()
(b) GoToMsgpack() / GoToJson()
(3) provided by tinylib/msgp, and by ugorji/go/codec
by using pre-compiled or just decoding into an instance
of the struct.
(4) see herein
(a) SexpToMsgpack() and SexpToJson()
(b) MsgpackToSexp(); uses (4) = (2) + (1)
(5) The SexpToGoStructs() and ToGoFunction() in this
file provide the capability of marshaling an
s-expression to a Go-struct that has been
registered to be associated with a named
hash map using (defmap). See repl/gotypereg.go
to add your Go-struct constructor. From
the prompt, the (togo) function instantiates
a 'shadow' Go-struct whose data matches
that configured in the record.
*/
func JsonFunction(env *Zlisp, name string, args []Sexp) (Sexp, error) {
if len(args) != 1 {
return SexpNull, WrongNargs
}
switch name {
case "json":
str := SexpToJson(args[0])
return &SexpRaw{Val: []byte(str)}, nil
case "unjson":
raw, isRaw := args[0].(*SexpRaw)
if !isRaw {
return SexpNull, fmt.Errorf("unjson error: SexpRaw required, but we got %T instead.", args[0])
}
return JsonToSexp([]byte(raw.Val), env)
case "msgpack":
by, _ := SexpToMsgpack(args[0])
return &SexpRaw{Val: []byte(by)}, nil
case "unmsgpack":
raw, isRaw := args[0].(*SexpRaw)
if !isRaw {
return SexpNull, fmt.Errorf("unmsgpack error: SexpRaw required, but we got %T instead.", args[0])
}
return MsgpackToSexp([]byte(raw.Val), env)
default:
return SexpNull, fmt.Errorf("JsonFunction error: unrecognized function name: '%s'", name)
}
}
// json -> sexp. env is needed to handle symbols correctly
func JsonToSexp(json []byte, env *Zlisp) (Sexp, error) {
iface, err := JsonToGo(json)
if err != nil {
return nil, err
}
return GoToSexp(iface, env)
}
// sexp -> json
func SexpToJson(exp Sexp) string {
switch e := exp.(type) {
case *SexpHash:
return e.jsonHashHelper()
case *SexpArray:
return e.jsonArrayHelper()
case *SexpSymbol:
return `"` + e.name + `"`
default:
return exp.SexpString(nil)
}
}
func (hash *SexpHash) jsonHashHelper() string {
str := fmt.Sprintf(`{"Atype":"%s", `, hash.TypeName)
ko := []string{}
n := len(hash.KeyOrder)
if n == 0 {
return str[:len(str)-2] + "}"
}
for _, key := range hash.KeyOrder {
keyst := key.SexpString(nil)
ko = append(ko, keyst)
val, err := hash.HashGet(nil, key)
if err == nil {
str += `"` + keyst + `":`
str += string(SexpToJson(val)) + `, `
} else {
panic(err)
}
}
str += `"zKeyOrder":[`
for _, key := range ko {
str += `"` + key + `", `
}
if n > 0 {
str = str[:len(str)-2]
}
str += "]}"
VPrintf("\n\n final ToJson() str = '%s'\n", str)
return str
}
func (arr *SexpArray) jsonArrayHelper() string {
if len(arr.Val) == 0 {
return "[]"
}
str := "[" + SexpToJson(arr.Val[0])
for _, sexp := range arr.Val[1:] {
str += ", " + SexpToJson(sexp)
}
return str + "]"
}
type msgpackHelper struct {
initialized bool
mh codec.MsgpackHandle
jh codec.JsonHandle
}
func (m *msgpackHelper) init() {
if m.initialized {
return
}
m.mh.MapType = reflect.TypeOf(map[string]interface{}(nil))
// configure extensions
// e.g. for msgpack, define functions and enable Time support for tag 1
//does this make a differenece? m.mh.AddExt(reflect.TypeOf(time.Time{}), 1, timeEncExt, timeDecExt)
m.mh.RawToString = true
m.mh.WriteExt = true
m.mh.SignedInteger = true
m.mh.Canonical = true // sort maps before writing them
// JSON
m.jh.MapType = reflect.TypeOf(map[string]interface{}(nil))
m.jh.SignedInteger = true
m.jh.Canonical = true // sort maps before writing them
m.initialized = true
}
var msgpHelper msgpackHelper
func init() {
msgpHelper.init()
}
// translate to sexp -> json -> go -> msgpack
// returns both the msgpack []bytes and the go intermediary
func SexpToMsgpack(exp Sexp) ([]byte, interface{}) {
json := []byte(SexpToJson(exp))
iface, err := JsonToGo(json)
panicOn(err)
by, err := GoToMsgpack(iface)
panicOn(err)
return by, iface
}
// json -> go
func JsonToGo(json []byte) (interface{}, error) {
var iface interface{}
decoder := codec.NewDecoderBytes(json, &msgpHelper.jh)
err := decoder.Decode(&iface)
if err != nil {
panic(err)
}
VPrintf("\n decoded type : %T\n", iface)
VPrintf("\n decoded value: %#v\n", iface)
return iface, nil
}
func GoToMsgpack(iface interface{}) ([]byte, error) {
var w bytes.Buffer
enc := codec.NewEncoder(&w, &msgpHelper.mh)
err := enc.Encode(&iface)
if err != nil {
return nil, err
}
return w.Bytes(), nil
}
// go -> json
func GoToJson(iface interface{}) []byte {
var w bytes.Buffer
encoder := codec.NewEncoder(&w, &msgpHelper.jh)
err := encoder.Encode(&iface)
if err != nil {
panic(err)
}
return w.Bytes()
}
// msgpack -> sexp
func MsgpackToSexp(msgp []byte, env *Zlisp) (Sexp, error) {
iface, err := MsgpackToGo(msgp)
if err != nil {
return nil, fmt.Errorf("MsgpackToSexp failed at MsgpackToGo step: '%s", err)
}
sexp, err := GoToSexp(iface, env)
if err != nil {
return nil, fmt.Errorf("MsgpackToSexp failed at GoToSexp step: '%s", err)
}
return sexp, nil
}
// msgpack -> go
func MsgpackToGo(msgp []byte) (interface{}, error) {
var iface interface{}
dec := codec.NewDecoderBytes(msgp, &msgpHelper.mh)
err := dec.Decode(&iface)
if err != nil {
return nil, err
}
//fmt.Printf("\n decoded type : %T\n", iface)
//fmt.Printf("\n decoded value: %#v\n", iface)
return iface, nil
}
// convert iface, which will typically be map[string]interface{},
// into an s-expression
func GoToSexp(iface interface{}, env *Zlisp) (Sexp, error) {
return decodeGoToSexpHelper(iface, 0, env, false), nil
}
func decodeGoToSexpHelper(r interface{}, depth int, env *Zlisp, preferSym bool) (s Sexp) {
VPrintf("decodeHelper() at depth %d, decoded type is %T\n", depth, r)
switch val := r.(type) {
case string:
//VPrintf("depth %d found string case: val = %#v\n", depth, val)
if preferSym {
return env.MakeSymbol(val)
}
return &SexpStr{S: val}
case int:
VPrintf("depth %d found int case: val = %#v\n", depth, val)
return &SexpInt{Val: int64(val)}
case int32:
VPrintf("depth %d found int32 case: val = %#v\n", depth, val)
return &SexpInt{Val: int64(val)}
case int64:
VPrintf("depth %d found int64 case: val = %#v\n", depth, val)
return &SexpInt{Val: val}
case float64:
VPrintf("depth %d found float64 case: val = %#v\n", depth, val)
return &SexpFloat{Val: val}
case []interface{}:
VPrintf("depth %d found []interface{} case: val = %#v\n", depth, val)
slice := []Sexp{}
for i := range val {
slice = append(slice, decodeGoToSexpHelper(val[i], depth+1, env, preferSym))
}
return &SexpArray{Val: slice, Env: env}
case map[string]interface{}:
VPrintf("depth %d found map[string]interface case: val = %#v\n", depth, val)
sortedMapKey, sortedMapVal := makeSortedSlicesFromMap(val)
pairs := make([]Sexp, 0)
typeName := "hash"
var keyOrd Sexp
foundzKeyOrder := false
for i := range sortedMapKey {
// special field storing the name of our record (defmap) type.
VPrintf("\n i=%d sortedMapVal type %T, value=%v\n", i, sortedMapVal[i], sortedMapVal[i])
VPrintf("\n i=%d sortedMapKey type %T, value=%v\n", i, sortedMapKey[i], sortedMapKey[i])
if sortedMapKey[i] == "zKeyOrder" {
keyOrd = decodeGoToSexpHelper(sortedMapVal[i], depth+1, env, true)
foundzKeyOrder = true
} else if sortedMapKey[i] == "Atype" {
tn, isString := sortedMapVal[i].(string)
if isString {
typeName = string(tn)
}
} else {
sym := env.MakeSymbol(sortedMapKey[i])
pairs = append(pairs, sym)
ele := decodeGoToSexpHelper(sortedMapVal[i], depth+1, env, preferSym)
pairs = append(pairs, ele)
}
}
hash, err := MakeHash(pairs, typeName, env)
if foundzKeyOrder {
err = SetHashKeyOrder(hash, keyOrd)
panicOn(err)
}
panicOn(err)
return hash
case []byte:
VPrintf("depth %d found []byte case: val = %#v\n", depth, val)
return &SexpRaw{Val: val}
case nil:
return SexpNull
case bool:
return &SexpBool{Val: val}
case *SexpReflect:
return decodeGoToSexpHelper(val.Val.Interface(), depth+1, env, preferSym)
case time.Time:
return &SexpTime{Tm: val}
default:
// do we have a struct for it?
nm := fmt.Sprintf("%T", val)
rt := GoStructRegistry.Lookup(nm)
if rt == nil {
fmt.Printf("unknown type '%s' in type switch, val = %#v. type = %T.\n", nm, val, val)
} else {
fmt.Printf("TODO: known struct '%s' in GoToSexp(), val = %#v. type = %T. TODO: make a record for it.\n", nm, val, val)
}
return SexpNull
}
return s
}
//msgp:ignore mapsorter KiSlice
type mapsorter struct {
key string
iface interface{}
}
type KiSlice []*mapsorter
func (a KiSlice) Len() int { return len(a) }
func (a KiSlice) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a KiSlice) Less(i, j int) bool { return a[i].key < a[j].key }
func makeSortedSlicesFromMap(m map[string]interface{}) ([]string, []interface{}) {
key := make([]string, len(m))
val := make([]interface{}, len(m))
so := make(KiSlice, 0)
for k, i := range m {
so = append(so, &mapsorter{key: k, iface: i})
}
sort.Sort(so)
for i := range so {
key[i] = so[i].key
val[i] = so[i].iface
}
return key, val
}
// translate an Sexpr to a go value that doesn't
// depend on any Sexp/Zlisp types. Zlisp maps
// will get turned into map[string]interface{}.
// This is mostly just an exercise in type conversion.
//
// on first entry, dedup can be nil. We use it to write the
// same pointer for a SexpHash used in more than one place.
//
func SexpToGo(sexp Sexp, env *Zlisp, dedup map[*SexpHash]interface{}) (result interface{}) {
cacheHit := false
if dedup == nil {
dedup = make(map[*SexpHash]interface{})
}
defer func() {
recov := recover()
if !cacheHit && recov == nil {
asHash, ok := sexp.(*SexpHash)
if ok {
// cache it. we might be overwriting with
// ourselves, but faster to just write again
// than to read and compare then write.
dedup[asHash] = result
//P("dedup caching in SexpToGo for hash %p / name='%s'", result, asHash.TypeName)
}
}
if recov != nil {
panic(recov)
} else {
tc, ok := result.(TypeCheckable)
if ok {
err := tc.TypeCheck()
if err != nil {
panic(fmt.Errorf("TypeCheck() error in zygo.SexpToGo for '%T': '%v'", result, err))
}
}
}
}()
switch e := sexp.(type) {
case *SexpRaw:
return []byte(e.Val)
case *SexpArray:
//P("*SexpArray decoding! e.Val='%#v'", e.Val)
ar := make([]interface{}, len(e.Val))
for i, ele := range e.Val {
ar[i] = SexpToGo(ele, env, dedup)
}
return ar
case *SexpInt:
// ugorji msgpack will give us int64 not int,
// so match that to make the decodings comparable.
return int64(e.Val)
case *SexpStr:
return e.S
case *SexpChar:
return rune(e.Val)
case *SexpFloat:
return float64(e.Val)
case *SexpHash:
// check dedup cache to see if we already generated a Go
// struct for this *SexpHash.
if alreadyGo, already := dedup[e]; already {
//P("SexpToGo dedup cache HIT! woot! alreadyGo = '%v' for src.TypeName='%v'", alreadyGo, e.TypeName)
cacheHit = true
return alreadyGo
}
m := make(map[string]interface{})
for _, arr := range e.Map {
for _, pair := range arr {
key := SexpToGo(pair.Head, env, dedup)
val := SexpToGo(pair.Tail, env, dedup)
keyString, isStringKey := key.(string)
if !isStringKey {
panic(fmt.Errorf("key '%v' should have been a string, but was not.", key))
}
m[keyString] = val
}
}
m["Atype"] = e.TypeName
ko := make([]interface{}, 0)
for _, k := range e.KeyOrder {
ko = append(ko, SexpToGo(k, env, dedup))
}
m["zKeyOrder"] = ko
return m
case *SexpPair:
// no conversion
return e
case *SexpSymbol:
return e.name
case *SexpFunction:
// no conversion done
return e
case *SexpSentinel:
// no conversion done
return e
case *SexpBool:
return e.Val
default:
fmt.Printf("\n error: unknown type: %T in '%#v'\n", e, e)
}
return nil
}
func ToGoFunction(env *Zlisp, name string, args []Sexp) (Sexp, error) {
if len(args) != 1 {
return SexpNull, WrongNargs
}
switch asHash := args[0].(type) {
default:
return SexpNull, fmt.Errorf("ToGoFunction (togo) error: value must be a hash or defmap; we see '%T'", args[0])
case *SexpHash:
tn := asHash.TypeName
//P("ToGo: SexpHash for tn='%s', shadowSet='%v'", tn, asHash.ShadowSet)
var err error
var newStruct interface{}
if asHash.ShadowSet && asHash.GoShadowStructVa.Kind() != reflect.Invalid {
//P("ToGo: tn '%s' already has GoShadowStruct, not making a new one", tn)
// don't return early, because we may have updates after changes
// from the sexp hashtable side, so just set newStruct to the old
// value and then let SexpToGoStructs() happen again.
//return &SexpStr{S: fmt.Sprintf("%#v", asHash.GoShadowStruct)}, nil
newStruct = asHash.GoShadowStruct
} else {
//P("ToGo: tn '%s' does not have GoShadowStruct set, making a new one", tn)
factory, hasMaker := GoStructRegistry.Registry[tn]
if !hasMaker {
return SexpNull, fmt.Errorf("type '%s' not registered in GoStructRegistry", tn)
}
newStruct, err = factory.Factory(env, asHash)
if err != nil {
return SexpNull, err
}
}
_, err = SexpToGoStructs(asHash, newStruct, env, nil)
if err != nil {
return SexpNull, err
}
// give new go struct a chance to boot up.
if env.booter != nil {
env.booter(newStruct)
}
asHash.GoShadowStruct = newStruct
asHash.GoShadowStructVa = reflect.ValueOf(newStruct)
asHash.ShadowSet = true
return &SexpStr{S: fmt.Sprintf("%#v", newStruct)}, nil
}
}
func FromGoFunction(env *Zlisp, name string, args []Sexp) (Sexp, error) {
if len(args) != 1 {
return SexpNull, WrongNargs
}
var sr *SexpReflect
switch x := args[0].(type) {
case *SexpReflect:
sr = x
return GoToSexp(x, env)
case *SexpArraySelector:
y, err := x.RHS(env)
if err != nil {
return SexpNull, err
}
switch z := y.(type) {
case *SexpReflect:
sr = z
default:
return SexpNull, fmt.Errorf("%s error: only works on *SexpReflect types. We saw %T inside an array selector", name, y)
}
default:
return SexpNull, fmt.Errorf("%s error: only works on *SexpReflect types. We saw %T", name, args[0])
}
return GoToSexp(sr.Val.Interface(), env)
return SexpNull, nil
}
func GoonDumpFunction(env *Zlisp, name string, args []Sexp) (Sexp, error) {
if len(args) != 1 {
return SexpNull, WrongNargs
}
fmt.Printf("\n")
goon.Dump(args[0])
return SexpNull, nil
}
// try to convert to registered go structs if possible,
// filling in the structure of target (should be a pointer).
func SexpToGoStructs(
sexp Sexp,
target interface{},
env *Zlisp,
dedup map[*SexpHash]interface{},
) (result interface{}, err error) {
Q("top of SexpToGoStructs")
cacheHit := false
if dedup == nil {
dedup = make(map[*SexpHash]interface{})
}
var recordKey string
defer func() {
_ = cacheHit
recov := recover()
if !cacheHit && err == nil && recov == nil {
asHash, ok := sexp.(*SexpHash)
if ok {
// cache it. we might be overwriting with
// ourselves, but faster to just write again
// than to read and compare then write.
dedup[asHash] = result
//P("dedup caching in SexpToGoStructs '%v' for hash name='%s'", result, asHash.TypeName)
}
}
if recov != nil {
panic(fmt.Errorf("last recordKey field name was '%s'. Caught panic: '%v'", recordKey, recov))
} else {
tc, ok := result.(TypeCheckable)
if ok {
err := tc.TypeCheck()
if err != nil {
panic(fmt.Errorf("TypeCheck() error in zygo.SexpToGoStructs for '%T': '%v'", result, err))
}
}
}
}()
Q(" 88888 entering SexpToGoStructs() with sexp=%#v and target=%#v of type %s", sexp, target, reflect.ValueOf(target).Type())
defer func() {
Q(" 99999 leaving SexpToGoStructs() with sexp='%#v' and target=%#v", sexp, target)
}()
targetIsSinglePtr := IsExactlySinglePointer(target)
targetIsDoublePtr := IsExactlyDoublePointer(target)
if !targetIsSinglePtr && !targetIsDoublePtr {
Q("is not exactly single or double pointer!!")
panic(fmt.Errorf("SexpToGoStructs() got bad target: was not *T single level pointer, but rather %s / %T", reflect.ValueOf(target).Type(), target))
}
// target is a pointer to our payload.
// targVa is a pointer to that same payload.
targVa := reflect.ValueOf(target)
targTyp := targVa.Type()
targKind := targVa.Kind()
targElemTyp := targTyp.Elem()
targElemKind := targElemTyp.Kind()
Q(" targVa is '%#v'", targVa)
if targKind != reflect.Ptr {
// panic(fmt.Errorf("SexpToGoStructs got non-pointer type! was type %T/val=%#v. targKind=%#v targTyp=%#v targVa=%#v", target, target, targKind, targTyp, targVa))
}
switch src := sexp.(type) {
case *SexpRaw:
targVa.Elem().Set(reflect.ValueOf([]byte(src.Val)))
case *SexpArray:
//Q(" starting 5555555555 on SexpArray")
if targElemKind != reflect.Array && targElemKind != reflect.Slice {
panic(fmt.Errorf("tried to translate from SexpArray into non-array/type: %v", targKind))
}
// allocate the slice
n := len(src.Val)
slc := reflect.MakeSlice(targElemTyp, 0, n)
//P(" slc starts out as %v/type = %T", slc, slc.Interface())
// if targ is *[]int, then targElem is []int, targElem.Elem() is int.
eTyp := targElemTyp.Elem()
for i, ele := range src.Val {
_ = i
goElem := reflect.New(eTyp) // returns pointer to new value
//P(" goElem = %#v before filling i=%d", goElem, i)
if _, err := SexpToGoStructs(ele, goElem.Interface(), env, dedup); err != nil {
return nil, err
}
//P(" goElem = %#v after filling i=%d", goElem, i)
//P(" goElem.Elem() = %#v after filling i=%d", goElem.Elem(), i)
slc = reflect.Append(slc, goElem.Elem())
//P(" slc after i=%d is now %v", i, slc)
}
targVa.Elem().Set(slc)
//P(" targVa is now %v", targVa)
case *SexpInt:
// ugorji msgpack will give us int64 not int,
// so match that to make the decodings comparable.
//P("*SexpInt code src.Val='%#v'.. targVa.Elem()='%#v'/Type: %T", src.Val, targVa.Elem().Interface(), targVa.Elem().Interface())
switch targVa.Elem().Interface().(type) {
case float64:
targVa.Elem().SetFloat(float64(src.Val))
case int64:
targVa.Elem().SetInt(int64(src.Val))
default:
targVa.Elem().SetInt(int64(src.Val))
}
case *SexpStr:
targVa.Elem().SetString(src.S)
case *SexpChar:
targVa.Elem().Set(reflect.ValueOf(rune(src.Val)))
case *SexpFloat:
switch targVa.Elem().Interface().(type) {
case int64:
targVa.Elem().SetInt(int64(src.Val))
case float64:
targVa.Elem().SetFloat(float64(src.Val))
default:
targVa.Elem().SetFloat(float64(src.Val))
}
case *SexpHash:
Q(" ==== found SexpHash")
// check dedup cache to see if we already generated a Go
// struct for this *SexpHash.
if alreadyGoStruct, already := dedup[src]; already {
Q("SexpToGoStructs dedup cache HIT! woot! alreadyGoStruct = '%v' for src.TypeName='%v'", alreadyGoStruct, src.TypeName)
// already did it. Return alreadyGoStruct.
cacheHit = true
vo := reflect.ValueOf(alreadyGoStruct).Elem()
targVa.Elem().Set(vo)
return target, nil
}
tn := src.TypeName
Q("tn='%s', target.(type) == %T", tn, target)
if tn == "hash" {
// not done with 'hash' translation to Go, targTyp.Elem().Kind()='map', targTyp.Elem()='map[string]float64'
//P(fmt.Sprintf("not done with 'hash' translation to Go, targTyp.Elem().Kind()='%v', targTyp.Elem()='%v'", targTyp.Elem().Kind(), targTyp.Elem()))
switch target.(type) {
case *map[string]string:
m := make(map[string]string)
for _, arr := range src.Map {
for _, pair := range arr {
key := SexpToGo(pair.Head, env, dedup)
val := SexpToGo(pair.Tail, env, dedup)
keys, isstr := key.(string)
if !isstr {
panic(fmt.Errorf("key '%v' should have been an string, but was not.", key))
}
vals, isstr := val.(string)
if !isstr {
panic(fmt.Errorf("val '%v' should have been an string, but was not.", val))
}
m[keys] = vals
}
}
targVa.Elem().Set(reflect.ValueOf(m))
return target, nil
case *map[int64]float64:
//P("target is a map[int64]float64")
m := make(map[int64]float64)
for _, arr := range src.Map {
for _, pair := range arr {
key := SexpToGo(pair.Head, env, dedup)
val := SexpToGo(pair.Tail, env, dedup)
keyint64, isint64Key := key.(int64)
if !isint64Key {
panic(fmt.Errorf("key '%v' should have been an int64, but was not.", key))
}
switch x := val.(type) {
case float64:
m[keyint64] = x
case int64:
m[keyint64] = float64(x)
default:
panic(fmt.Errorf("val '%v' should have been an float64, but was not.", val))
}
}
}
targVa.Elem().Set(reflect.ValueOf(m))
return target, nil
}
panic("not done here yet")
// TODO: don't try to translate into a Go struct,
// but instead... what? just a map[string]interface{}
//return nil, nil
}
switch targTyp.Elem().Kind() {
case reflect.Interface:
// could be an Interface like Flyer here, that contains the struct.
case reflect.Struct:
// typical case
case reflect.Ptr:
// pointer to struct we know? if we have a factory for it below
default:
Q("problem! elem kind not recognized: '%#v'/type='%T'", targTyp.Elem().Kind(), targTyp.Elem().Kind())
panic(fmt.Errorf("tried to translate from SexpHash record into non-struct/type: %v / targType.Elem().Kind()=%v", targKind, targTyp.Elem().Kind()))
}
// use targVa, but check against the type in the registry for sanity/type checking.
factory, hasMaker := GoStructRegistry.Registry[tn]
if !hasMaker {
panic(fmt.Errorf("type '%s' not registered in GoStructRegistry", tn))
//return nil, fmt.Errorf("type '%s' not registered in GoStructRegistry", tn)
}
//P("factory = %#v targTyp.Kind=%s", factory, targTyp.Kind())
checkPtrStruct, err := factory.Factory(env, src)
if err != nil {
return nil, err
}
factOutputVal := reflect.ValueOf(checkPtrStruct)
factType := factOutputVal.Type()
if targTyp.Kind() == reflect.Ptr && targTyp.Elem().Kind() == reflect.Interface && factType.Implements(targTyp.Elem()) {
Q(" accepting type check: %v implements %v", factType, targTyp)
// also here we need to allocate an actual struct in place of
// the interface
// caller has a pointer to an interface
// and we just want to set that interface to point to us.
targVa.Elem().Set(factOutputVal) // tell our caller
// now fill into this concrete type
targVa = factOutputVal // tell the code below
targTyp = targVa.Type()
targKind = targVa.Kind()
src.ShadowSet = true
src.GoShadowStruct = checkPtrStruct
src.GoShadowStructVa = factOutputVal
} else if targTyp.Kind() == reflect.Ptr && targTyp.Elem() == factType {
Q("we have a double pointer that matches the factory type! factType == targTyp.Elem(). factType=%v/%T targTyp = %v/%T", factType, factType, targTyp, targTyp)
Q(" targTyp.Elem() = %v", targTyp.Elem())
targVa.Elem().Set(factOutputVal) // tell our caller
// now fill into this concrete type
targVa = factOutputVal // tell the code below
targTyp = targVa.Type()
targKind = targVa.Kind()
src.ShadowSet = true
src.GoShadowStruct = checkPtrStruct
src.GoShadowStructVa = factOutputVal
} else if factType != targTyp {
// factType=*zygo.NestInner/*reflect.rtype targTyp = **zygo.NestInner/*reflect.rtype
Q("factType != targTyp. factType=%v/%T targTyp = %v/%T", factType, factType, targTyp, targTyp)
Q(" targTyp.Elem() = %v", targTyp.Elem())
panic(fmt.Errorf("type checking failed compare the factor associated with SexpHash and the provided target *T: expected '%s' (associated with typename '%s' in the GoStructRegistry) but saw '%s' type in target", tn, factType, targTyp))
}
//maploop:
for _, arr := range src.Map {
for _, pair := range arr {
recordKey = ""
switch k := pair.Head.(type) {
case *SexpStr:
recordKey = k.S
case *SexpSymbol:
recordKey = k.name
default:
fmt.Printf(" skipping field '%#v' which we don't know how to lookup.", pair.Head)
panic(fmt.Sprintf("unknown fields disallowed: we didn't recognize '%#v'", pair.Head))
continue
}
// We've got to match pair.Head to
// one of the struct fields: we'll use
// the json tags for that. Or their
// full exact name if they didn't have
// a json tag.
Q(" JsonTagMap = %#v", src.JsonTagMap)
det, found := src.JsonTagMap[recordKey]
if !found {
// try once more, with uppercased version
// of record key
upperKey := strings.ToUpper(recordKey[:1]) + recordKey[1:]
det, found = src.JsonTagMap[upperKey]
if !found {
fmt.Printf(" skipping field '%s' in this hash/which we could not find in the JsonTagMap", recordKey)
panic(fmt.Sprintf("unkown field '%s' not allowed; could not find in the JsonTagMap. Fieldnames are case sensitive.", recordKey))
continue
}
}
Q(" **** recordKey = '%s'\n", recordKey)
Q(" we found in pair.Tail: %T !", pair.Tail)
dref := targVa.Elem()
Q(" deref = %#v / type %T", dref, dref)
Q(" det = %#v", det)
// fld should hold our target when
// done recursing through any embedded structs.
// TODO: handle embedded pointers to structs too.
var fld reflect.Value
Q(" we have an det.EmbedPath of '%#v'", det.EmbedPath)
// drill down to the actual target
fld = dref
for i, p := range det.EmbedPath {
Q("about to call fld.Field(%d) on fld = '%#v'/type=%T", p.ChildFieldNum, fld, fld)
fld = fld.Field(p.ChildFieldNum)
Q(" dropping down i=%d through EmbedPath at '%s', fld = %#v ", i, p.ChildName, fld)
}
Q(" fld = %#v ", fld)
// INVAR: fld points at our target to fill
ptrFld := fld.Addr()
tmp, needed := unexportHelper(&ptrFld, &fld)
if needed {
ptrFld = *tmp
}
_, err := SexpToGoStructs(pair.Tail, ptrFld.Interface(), env, dedup)
if err != nil {
panic(err)
//return nil, err
}
}
}
case *SexpPair:
panic("unimplemented")
// no conversion
//return src
case *SexpSymbol:
targVa.Elem().SetString(src.name)
case *SexpFunction:
panic("unimplemented: *SexpFunction converstion.")
// no conversion done
//return src
case *SexpSentinel:
// set to nil
targVa.Elem().Set(reflect.Zero(targVa.Type().Elem()))
case *SexpTime:
targVa.Elem().Set(reflect.ValueOf(src.Tm))
case *SexpBool:
targVa.Elem().Set(reflect.ValueOf(src.Val))
default:
fmt.Printf("\n error: unknown type: %T in '%#v'\n", src, src)
}
return target, nil
}
/*
if accessing unexported fields, we'll recover from
panic: reflect.Value.Interface: cannot return value obtained from unexported field or method
and use this technique
https://stackoverflow.com/questions/42664837/access-unexported-fields-in-golang-reflect
*/
func unexportHelper(ptrFld *reflect.Value, fld *reflect.Value) (r *reflect.Value, needed bool) {
defer func() {
recov := recover()
if recov != nil {
//P("unexportHelper recovering from '%v'", recov)
e := reflect.NewAt(fld.Type(), unsafe.Pointer(fld.UnsafeAddr()))
r = &e
needed = true
}
}()
// can we do this without panic?
_ = ptrFld.Interface()
// if no panic, return same.
return ptrFld, false
}
// A small set of important little buildling blocks.
// These demonstrate how to use reflect.
/*
(1) Tutorial on setting structs with reflect.Set()
http://play.golang.org/p/sDmFgZmGvv
package main
import (
"fmt"
"reflect"
)
type A struct {
S string
}
func MakeA() interface{} {
return &A{}
}
func main() {
a1 := MakeA()
a2 := MakeA()
a2.(*A).S = "two"
// now assign a2 -> a1 using reflect.
targVa := reflect.ValueOf(&a1).Elem()
targVa.Set(reflect.ValueOf(a2))
fmt.Printf("a1 = '%#v' / '%#v'\n", a1, targVa.Interface())
}
// output
// a1 = '&main.A{S:"two"}' / '&main.A{S:"two"}'
(2) Tutorial on setting fields inside a struct with reflect.Set()
http://play.golang.org/p/1k4iQKVwUD
package main
import (
"fmt"
"reflect"
)
type A struct {
S string
}
func main() {
a1 := &A{}
three := "three"
fld := reflect.ValueOf(&a1).Elem().Elem().FieldByName("S")
fmt.Printf("fld = %#v\n of type %T\n", fld, fld)
fmt.Println("settability of fld:", fld.CanSet()) // true
// now assign to field a1.S the string "three" using reflect.
fld.Set(reflect.ValueOf(three))
fmt.Printf("after fld.Set(): a1 = '%#v' \n", a1)
}
// output:
fld = ""
of type reflect.Value
settability of fld: true
after fld.Set(): a1 = '&main.A{S:"three"}'
(3) Setting struct after passing through an function call interface{} param:
package main
import (
"fmt"
"reflect"
)
type A struct {
S string
}
func main() {
a1 := &A{}
f(&a1)
fmt.Printf("a1 = '%#v'\n", a1)
// a1 = '&main.A{S:"two"}' / '&main.A{S:"two"}'
}
func f(i interface{}) {
a2 := MakeA()
a2.(*A).S = "two"
// now assign a2 -> a1 using reflect.
//targVa := reflect.ValueOf(&a1).Elem()
targVa := reflect.ValueOf(i).Elem()
targVa.Set(reflect.ValueOf(a2))
}
(4) using a function to do the Set(), and checking
the received interface for correct type.
Also: Using a function to set just one sub-field.
package main
import (
"fmt"
"reflect"
)
type A struct {
S string
R string
}
func main() {
a1 := &A{}
overwrite_contents_of_struct(a1)
fmt.Printf("a1 = '%#v'\n", a1)
// output:
// yes, is single level pointer
// a1 = '&main.A{S:"two", R:""}'
assignToOnlyFieldR(a1)
fmt.Printf("after assignToOnlyFieldR(a1): a1 = '%#v'\n", a1)
// output:
// yes, is single level pointer
// a1 = '&main.A{S:"two", R:""}'
// yes, is single level pointer
// fld = ""
// of type reflect.Value
// settability of fld: true
// after assignToOnlyFieldR(a1): a1 = '&main.A{S:"two", R:"R has been altered"}'
}
func assignToOnlyFieldR(i interface{}) {
if !IsExactlySinglePointer(i) {
panic("not single level pointer")
}
fmt.Printf("yes, is single level pointer\n")
altered := "R has been altered"
fld := reflect.ValueOf(i).Elem().FieldByName("R")
fmt.Printf("fld = %#v\n of type %T\n", fld, fld)
fmt.Println("settability of fld:", fld.CanSet()) // true
// now assign to field a1.S
fld.Set(reflect.ValueOf(altered))
}
func overwrite_contents_of_struct(i interface{}) {
// we want i to contain an *A, or a pointer-to struct.
// So we can reassign *ptr = A' for a different content A'.
if !IsExactlySinglePointer(i) {
panic("not single level pointer")
}
fmt.Printf("yes, is single level pointer\n")
a2 := &A{S: "two"}
// now assign a2 -> a1 using reflect.
targVa := reflect.ValueOf(i).Elem()
targVa.Set(reflect.ValueOf(a2).Elem())
}
func IsExactlySinglePointer(target interface{}) bool {
typ := reflect.ValueOf(target).Type()
kind := typ.Kind()
if kind != reflect.Ptr {
return false
}
typ2 := typ.Elem()
kind2 := typ2.Kind()
if kind2 == reflect.Ptr {
return false // two level pointer
}
return true
}
*/