package zygo import ( "errors" "fmt" "hash/fnv" "reflect" "strings" ) var NoAttachedGoStruct = fmt.Errorf("hash has no attach Go struct") func HashExpression(env *Zlisp, expr Sexp) (int, error) { hashcode, isList, err := hashHelper(expr) if err != nil { return 0, err } if !isList { return hashcode, nil } // can we evaluate it? if env != nil { res, err := EvalFunction(env, "eval-hash-key", []Sexp{expr}) if err != nil { return 0, fmt.Errorf("error during eval of "+ "hash key: %s", err) } // 2nd try hashcode2, isList2, err := hashHelper(res) if err != nil { return 0, fmt.Errorf("evaluated key function to '%s' but could not hash type %T: %s", res.SexpString(nil), res, err) } if !isList2 { return hashcode2, nil } return 0, fmt.Errorf("list '%s' found where hash key needed", res.SexpString(nil)) } // end if env == nil return 0, fmt.Errorf("cannot hash type %T", expr) } func hashHelper(expr Sexp) (hashcode int, isList bool, err error) { switch e := expr.(type) { case *SexpInt: return int(e.Val), false, nil case *SexpChar: return int(e.Val), false, nil case *SexpSymbol: return e.number, false, nil case *SexpStr: hasher := fnv.New32() _, err := hasher.Write([]byte(e.S)) if err != nil { return 0, false, err } return int(hasher.Sum32()), false, nil case *SexpPair: return 0, true, nil case *SexpArray: return int(Blake2bUint64([]byte(e.SexpString(nil)))), false, nil } return 0, false, fmt.Errorf("cannot hash type %T", expr) } func MakeHash(args []Sexp, typename string, env *Zlisp) (*SexpHash, error) { // Q("MakeHash called ") // for i := range args { // Q("MakeHash args[i=%v] = '%v'", i, args[i].SexpString(nil)) // } // when passed for example (hash [0]:12) we see // 3 args -- the colon is passed as the colon function; // so eliminate it as it is just an // extra unwanted element. This means we can never store // the colon function in a hash; that's okay; its // purpose is convenient syntax. args = env.EliminateColonAndCommaFromArgs(args) if len(args)%2 != 0 { return &SexpHash{Env: env}, errors.New("hash requires even number of arguments") } var memberCount int var arr SexpArray var fld SexpArray var meth = []reflect.Method{} var field = []reflect.StructField{} var va reflect.Value num := -1 var got reflect.Type var iface interface{} jsonMap := make(map[string]*HashFieldDet) factory := GoStructRegistry.Lookup(typename) if factory == nil { factory = &RegisteredType{Factory: MakeGoStructFunc(func(env *Zlisp, h *SexpHash) (interface{}, error) { return MakeHash(nil, typename, env) })} factory.Aliases = make(map[string]bool) } // how about UserStructDefn ? if TypeName != field/hash detOrder := []*HashFieldDet{} var zmain SexpFunction zmethods := make(map[string]*SexpFunction) var superClass *SexpHash var defnEnv *SexpHash //Q("generating SexpHash with typename: '%s'", typename) hash := SexpHash{ TypeName: typename, Map: make(map[int][]*SexpPair), KeyOrder: []Sexp{}, GoStructFactory: factory, NumKeys: memberCount, GoMethods: meth, GoMethSx: arr, GoFieldSx: fld, GoFields: field, NumMethod: num, GoType: got, JsonTagMap: jsonMap, GoShadowStructVa: va, GoShadowStruct: iface, DetOrder: detOrder, ZMain: zmain, ZMethods: zmethods, SuperClass: superClass, DefnEnv: defnEnv, Env: env, } k := 0 for i := 0; i < len(args); i += 2 { key := args[i] val := args[i+1] err := hash.HashSet(key, val) if err != nil { return &hash, err } k++ } //Q("doing factory, foundRecordType := GoStructRegistry.Registry[typename]") factoryShad, foundRecordType := GoStructRegistry.Registry[typename] if foundRecordType { //Q("factoryShad = '%#v' for typename='%s'\n", factoryShad, typename) if factoryShad.hasShadowStruct { //Q("\n in MakeHash: found struct associated with '%s'\n", typename) hash.SetGoStructFactory(factoryShad) //Q("\n in MakeHash: after SetGoStructFactory for typename '%s'\n", typename) err := hash.SetMethodList(env) if err != nil { return &SexpHash{Env: env}, fmt.Errorf("unexpected error "+ "from hash.SetMethodList(): %s", err) } } else { err := factoryShad.TypeCheckRecord(&hash) if err != nil { return &SexpHash{Env: env}, err } } } else { //Q("\n in MakeHash: did not find Go struct with typename = '%s'\n", typename) factory.initDone = true factory.ReflectName = typename factory.DisplayAs = typename GoStructRegistry.RegisterUserdef(factory, false, typename) } return &hash, nil } func (h *SexpHash) DotPathHashGet(env *Zlisp, sym *SexpSymbol) (Sexp, error) { path := DotPartsRegex.FindAllString(sym.name, -1) //Q("in DotPathHashGet(), path = '%#v'", path) if len(path) == 0 { return SexpNull, fmt.Errorf("internal error: DotPathHashGet" + " path had zero length") } // Q("\n in DotPathHashGet(), about to call nestedPathGetSet() with"+ // "path='%#v\n", path) exp, err := h.nestedPathGetSet(env, path, nil) if err != nil { return SexpNull, err } return exp, nil } func (hash *SexpHash) HashGet(env *Zlisp, key Sexp) (res Sexp, err error) { //Q("top of HashGet, key = '%v' of type %T", key.SexpString(nil), key) switch sym := key.(type) { case *SexpSymbol: if sym == nil { panic("cannot have nil symbol for key") } //P("HashGet, sym = '%v'. isDot=%v", sym.SexpString(nil), sym.isDot) if sym.isDot { return hash.DotPathHashGet(env, sym) } case *SexpArray: if len(sym.Val) == 1 { key = sym.Val[0] } } // this is kind of a hack // SexpEnd can't be created by user // so there is no way it would actually show up in the map val, err := hash.HashGetDefault(env, key, SexpEnd) if err != nil { return SexpNull, err } if val == SexpEnd { return SexpNull, fmt.Errorf("%s has no field '%s' [err 1]", hash.TypeName, key.SexpString(nil)) //return SexpNull, fmt.Errorf("%s has no field '%s'", hash.UserStructDefn.Name, key.SexpString(nil)) } return val, nil } func (hash *SexpHash) HashGetDefault(env *Zlisp, key Sexp, defaultval Sexp) (Sexp, error) { hashval, err := HashExpression(env, key) if err != nil { return SexpNull, err } //P("HashGetDefault, hashval='%#v', key='%s'", hashval, key.SexpString(nil)) //for kk := range hash.Map { // P("hash.Map has key '%#v'", kk) //} arr, ok := hash.Map[hashval] //P("arr='%#v', ok='%#v'", arr, ok) if !ok { return defaultval, nil } for _, pair := range arr { res, err := env.Compare(pair.Head, key) if err == nil && res == 0 { return pair.Tail, nil } } return defaultval, nil } var KeyNotSymbol = fmt.Errorf("key is not a symbol") func (h *SexpHash) TypeCheckField(key Sexp, val Sexp) error { //Q("in TypeCheckField, key='%v' val='%v'", key.SexpString(nil), val.SexpString(nil)) var keySym *SexpSymbol wasSym := false switch ks := key.(type) { case *SexpSymbol: keySym = ks wasSym = true default: return KeyNotSymbol } p := h.GoStructFactory if p == nil { //Q("SexpHash.TypeCheckField() sees nil GoStructFactory, bailing out.") return nil } else { //Q("SexpHash.TypeCheckField() sees h.GoStructFactory = '%#v'", h.GoStructFactory) } if p.UserStructDefn == nil { //Q("SexpHash.TypeCheckField() sees nil has.GoStructFactory.UserStructDefn, bailing out.") // check in the registry for this type! rt := GoStructRegistry.Lookup(h.TypeName) // was it found? If so, use it! if rt != nil && rt.UserStructDefn != nil { if rt.UserStructDefn.FieldType != nil { Q("") Q("we have a type for hash.TypeName = '%s', using it by "+ "replacing the hash.GoStructFactory with rt", h.TypeName) Q("") Q("old: h.GoStructFactory = '%#v'", h.GoStructFactory) Q("") Q("new: rt = '%#v'", rt) Q("new rt.UserStructDefn.FieldType = '%#v'", rt.UserStructDefn.FieldType) //p.UserStructDefn = rt.UserStructDefn h.GoStructFactory = rt p = h.GoStructFactory } } else { return nil } } // type-check record updates here, if we are a record with a // registered type associated. if wasSym && h.TypeName != "hash" && h.TypeName != "field" && p != nil { k := keySym.name Q("is key '%s' defined?", k) declaredTyp, ok := p.UserStructDefn.FieldType[k] if !ok { return fmt.Errorf("%s has no field '%s' [err 2]", p.UserStructDefn.Name, k) } obsTyp := val.Type() if obsTyp == nil { // allow certain types to be nil, e.g. [] and nil itself switch a := val.(type) { case *SexpArray: if len(a.Val) == 0 { return nil // okay } case *SexpSentinel: return nil // okay default: return fmt.Errorf("%v has nil Type", val.SexpString(nil)) } } Q("obsTyp is %T / val = %#v", obsTyp, obsTyp) Q("declaredTyp is %T / val = %#v", declaredTyp, declaredTyp) if obsTyp != declaredTyp { if obsTyp.RegisteredName == "[]" { if strings.HasPrefix(declaredTyp.RegisteredName, "[]") { // okay to assign empty slice to typed slice goto done } } return fmt.Errorf("field %v.%v is %v, cannot assign %v '%v'", p.UserStructDefn.Name, k, declaredTyp.SexpString(nil), obsTyp.SexpString(nil), val.SexpString(nil)) } } done: return nil } func (hash *SexpHash) HashSet(key Sexp, val Sexp) error { Q("in HashSet, key='%v' val='%v'", key.SexpString(nil), val.SexpString(nil)) if _, isComment := key.(*SexpComment); isComment { return fmt.Errorf("HashSet: key cannot be comment") } if _, isComment := val.(*SexpComment); isComment { return fmt.Errorf("HashSet: val cannot be comment") } err := hash.TypeCheckField(key, val) if err != nil { if err != KeyNotSymbol { return err } } hashval, err := HashExpression(nil, key) if err != nil { return err } arr, ok := hash.Map[hashval] if !ok { hash.Map[hashval] = []*SexpPair{Cons(key, val)} hash.KeyOrder = append(hash.KeyOrder, key) hash.NumKeys++ Q("in HashSet, added key to KeyOrder: '%v'", key) return nil } found := false for i, pair := range arr { res, err := hash.Env.Compare(pair.Head, key) if err == nil && res == 0 { arr[i] = Cons(key, val) found = true } } if !found { arr = append(arr, Cons(key, val)) hash.KeyOrder = append(hash.KeyOrder, key) hash.NumKeys++ } hash.Map[hashval] = arr return nil } func (hash *SexpHash) HashDelete(key Sexp) error { hashval, err := HashExpression(nil, key) if err != nil { return err } arr, ok := hash.Map[hashval] // if it doesn't exist, no need to delete it if !ok { return nil } hash.NumKeys-- for i, pair := range arr { res, err := hash.Env.Compare(pair.Head, key) if err == nil && res == 0 { hash.Map[hashval] = append(arr[0:i], arr[i+1:]...) break } } return nil } func HashCountKeys(hash *SexpHash) int { var num int for _, arr := range hash.Map { num += len(arr) } if num != hash.NumKeys { panic(fmt.Errorf("HashCountKeys disagreement on count: num=%d, (*hash.NumKeys)=%d", num, hash.NumKeys)) } return num } func HashIsEmpty(hash *SexpHash) bool { for _, arr := range hash.Map { if len(arr) > 0 { return false } } return true } func SetHashKeyOrder(hash *SexpHash, keyOrd Sexp) error { // truncate down to zero, then build back up correctly. hash.KeyOrder = hash.KeyOrder[:0] keys, isArr := keyOrd.(*SexpArray) if !isArr { return fmt.Errorf("must have SexpArray for keyOrd, but instead we have: %T with value='%#v'", keyOrd, keyOrd) } for _, key := range keys.Val { hash.KeyOrder = append(hash.KeyOrder, key) } return nil } func (hash *SexpHash) HashPairi(pos int) (*SexpPair, error) { nk := hash.NumKeys if pos > nk { return &SexpPair{}, fmt.Errorf("hpair error: pos %d is beyond our key count %d", pos, nk) } lenKeyOrder := len(hash.KeyOrder) var err error var key, val Sexp found := false for k := pos; k < lenKeyOrder; k++ { key = hash.KeyOrder[k] val, err = hash.HashGet(nil, key) if err == nil { found = true break } // what about deleted keys? just skip to the next! } if !found { panic(fmt.Errorf("hpair internal error: could not get element at pos %d in lenKeyOrder=%d", pos, lenKeyOrder)) } return Cons(key, &SexpPair{Head: val, Tail: SexpNull}), nil } func GoMethodListFunction(env *Zlisp, name string, args []Sexp) (Sexp, error) { if len(args) != 1 { return SexpNull, WrongNargs } h, isHash := args[0].(*SexpHash) if !isHash { return SexpNull, fmt.Errorf("hash/record required, but saw type %T/val=%#v", args[0], args[0]) } if h.NumMethod != -1 { // use cached results return &h.GoMethSx, nil } // TODO: do we really need this; couldn't we check h.ShadowSet instead? v, err := h.GoStructFactory.Factory(env, nil) if v == nil { return SexpNull, NoAttachedGoStruct } if err != nil { return SexpNull, fmt.Errorf("problem during h.GoStructFactory.Factory() call: '%v'", err) } h.SetMethodList(env) return env.NewSexpArray(h.GoMethSx.Val), nil } func (h *SexpHash) SetMethodList(env *Zlisp) error { Q("hash.SetMethodList() called.\n") if !h.GoStructFactory.hasShadowStruct { return NoAttachedGoStruct } rs, err := h.GoStructFactory.Factory(env, nil) if err != nil { return err } if rs == nil { return NoAttachedGoStruct } va := reflect.ValueOf(rs) ty := va.Type() n := ty.NumMethod() Q("hash.SetMethodList() sees %d methods on type %v\n", n, ty) h.NumMethod = n h.GoType = ty sx := make([]Sexp, n) sl := make([]reflect.Method, n) for i := 0; i < n; i++ { sl[i] = ty.Method(i) sx[i] = &SexpStr{S: sl[i].Name + " " + sl[i].Type.String()} } h.GoMethSx.Val = sx h.GoMethods = sl // do the fields too // gotta get the struct, not a pointer to it e := va.Elem() var notAStruct = reflect.Value{} if e == notAStruct { panic(fmt.Errorf("registered GoStruct for '%s' was not a struct?!", h.TypeName)) } tye := e.Type() fx := make([]Sexp, 0) fl := make([]reflect.StructField, 0) embeds := []EmbedPath{} json2ptr := make(map[string]*HashFieldDet) detOrder := make([]*HashFieldDet, 0) fillJsonMap(&json2ptr, &fx, &fl, embeds, tye, &detOrder) h.GoFieldSx.Val = fx h.GoFields = fl h.JsonTagMap = json2ptr h.DetOrder = detOrder return nil } const YesIamEmbeddedAbove = true // recursively fill with embedded/anonymous types as well func fillJsonMap(json2ptr *map[string]*HashFieldDet, fx *[]Sexp, fl *[]reflect.StructField, embedPath []EmbedPath, tye reflect.Type, detOrder *[]*HashFieldDet) { var suffix string if len(embedPath) > 0 { suffix = fmt.Sprintf(" embed-path<%s>", GetEmbedPath(embedPath)) } m := tye.NumField() for i := 0; i < m; i++ { fld := tye.Field(i) *fl = append(*fl, fld) *fx = append(*fx, &SexpStr{S: fld.Name + " " + fld.Type.String() + suffix}) det := &HashFieldDet{ FieldNum: i, FieldType: fld.Type, StructField: fld, FieldName: fld.Name, FieldJsonTag: fld.Name, // fallback. changed below if json tag available. } jsonTag := fld.Tag.Get("json") if jsonTag != "" { det.FieldJsonTag = jsonTag (*json2ptr)[jsonTag] = det } else { (*json2ptr)[fld.Name] = det } *detOrder = append(*detOrder, det) det.EmbedPath = append(embedPath, EmbedPath{ChildName: fld.Name, ChildFieldNum: i}) if fld.Anonymous { // track how to get at embedded struct fields fillJsonMap(json2ptr, fx, fl, det.EmbedPath, fld.Type, detOrder) } } } func GoFieldListFunction(env *Zlisp, name string, args []Sexp) (Sexp, error) { if len(args) != 1 { return SexpNull, WrongNargs } h, isHash := args[0].(*SexpHash) if !isHash { return SexpNull, fmt.Errorf("hash/record required, but saw %T/val=%v", args[0], args[0]) } if !h.GoStructFactory.hasShadowStruct { return SexpNull, NoAttachedGoStruct } v, err := h.GoStructFactory.Factory(env, nil) if v == nil { return SexpNull, NoAttachedGoStruct } if err != nil { return SexpNull, fmt.Errorf("problem during h.GoStructFactory.Factory() call: '%v'", err) } return &h.GoFieldSx, nil } // works over hashes and arrays func GenericHpairFunction(env *Zlisp, name string, args []Sexp) (Sexp, error) { if len(args) != 2 { return SexpNull, WrongNargs } posreq, isInt := args[1].(*SexpInt) if !isInt { return SexpNull, fmt.Errorf("hpair position request must be an integer") } pos := int(posreq.Val) switch seq := args[0].(type) { case *SexpHash: if pos < 0 || pos >= len(seq.KeyOrder) { return SexpNull, fmt.Errorf("hpair position request %d out of bounds", pos) } return seq.HashPairi(pos) case *SexpArray: if pos < 0 || pos >= len(seq.Val) { return SexpNull, fmt.Errorf("hpair position request %d out of bounds", pos) } return Cons(&SexpInt{Val: int64(pos)}, Cons(seq.Val[pos], SexpNull)), nil default: return SexpNull, errors.New("first argument of to hpair function must be hash, list, or array") } //return SexpNull, nil } func (h *SexpHash) FillHashFromShadow(env *Zlisp, src interface{}) error { Q("in FillHashFromShadow, with src = %#v", src) h.GoShadowStruct = src h.ShadowSet = true vaSrc := reflect.ValueOf(src).Elem() for i, det := range h.DetOrder { Q("\n looking at det for %s; %v-th entry in h.DetOrder\n", det.FieldJsonTag, i) goField := vaSrc.Field(det.FieldNum) val, err := fillHashHelper(goField.Interface(), 0, env, false) if err != nil { Q("got err='%s' back from fillHashhelper", err) return fmt.Errorf("error on GoToSexp for field '%s': '%s'", det.FieldJsonTag, err) } Q("got err==nil back from fillHashhelper; key=%#v, val=%#v", det.FieldJsonTag, val) key := env.MakeSymbol(det.FieldJsonTag) err = h.HashSet(key, val) if err != nil { return fmt.Errorf("error on HashSet for key '%s': '%s'", key.SexpString(nil), err) } } return nil } // translate Go -> Sexp, assuming hard *T{} struct pointers // for all Go structs func fillHashHelper(r interface{}, depth int, env *Zlisp, preferSym bool) (Sexp, error) { Q("fillHashHelper() at depth %d, decoded type is %T\n", depth, r) // check for one of our registered structs // go through the type registry upfront for hashName, factory := range GoStructRegistry.Registry { //P("fillHashHelper is trying hashName='%s'", hashName) st, err := factory.Factory(env, nil) if err != nil { return SexpNull, err } if reflect.ValueOf(st).Type() == reflect.ValueOf(r).Type() { Q("we have a registered struct match for st=%T and r=%T", st, r) retHash, err := MakeHash([]Sexp{}, hashName, env) if err != nil { return SexpNull, fmt.Errorf("MakeHash '%s' problem: %s", hashName, err) } err = retHash.FillHashFromShadow(env, r) if err != nil { return SexpNull, err } Q("retHash = %#v\n", retHash) return retHash, nil // or return sx? } else { Q("fillHashHelper: no match for st=%T and r=%T", st, r) } } Q("fillHashHelper: trying basic non-struct types for r=%T", r) // now handle basic non struct types: switch val := r.(type) { case string: Q("depth %d found string case: val = %#v\n", depth, val) if preferSym { return env.MakeSymbol(val), nil } return &SexpStr{S: val}, nil case int: Q("depth %d found int case: val = %#v\n", depth, val) return &SexpInt{Val: int64(val)}, nil case int32: Q("depth %d found int32 case: val = %#v\n", depth, val) return &SexpInt{Val: int64(val)}, nil case int64: Q("depth %d found int64 case: val = %#v\n", depth, val) return &SexpInt{Val: int64(val)}, nil case float64: Q("depth %d found float64 case: val = %#v\n", depth, val) return &SexpFloat{Val: val}, nil case []interface{}: Q("depth %d found []interface{} case: val = %#v\n", depth, val) slice := []Sexp{} for i := range val { sx2, err := fillHashHelper(val[i], depth+1, env, preferSym) if err != nil { return SexpNull, fmt.Errorf("error in fillHashHelper() call: '%s'", err) } slice = append(slice, sx2) } return &SexpArray{Val: slice, Env: env}, nil case map[string]interface{}: Q("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. Q("\n i=%d sortedMapVal type %T, value=%v\n", i, sortedMapVal[i], sortedMapVal[i]) Q("\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, nil case []byte: Q("depth %d found []byte case: val = %#v\n", depth, val) return &SexpRaw{Val: val}, nil case nil: return SexpNull, nil case bool: return &SexpBool{Val: val}, nil default: Q("unknown type in type switch, val = %#v. type = %T.\n", val, val) } return SexpNull, nil } func (h *SexpHash) nestedPathGetSet(env *Zlisp, dotpaths []string, setVal *Sexp) (Sexp, error) { if len(dotpaths) == 0 { return SexpNull, fmt.Errorf("internal error: in nestedPathGetSet() dotpaths" + " had zero length") } var ret Sexp = SexpNull var err error askh := h lenpath := len(dotpaths) //Q("\n in nestedPathGetSet, dotpaths=%#v\n", dotpaths) for i := range dotpaths { if setVal != nil && i == lenpath-1 { // assign now err = askh.HashSet(env.MakeSymbol(dotpaths[i][1:]), *setVal) //P("\n i=%v in nestedPathGetSet, dotpaths[i][1:]='%v' call to "+ // "HashSet returned err = '%s'\n", i, dotpaths[i][1:], err) return *setVal, err } ret, err = askh.HashGet(env, env.MakeSymbol(dotpaths[i][1:])) //P("\n i=%v in nestedPathGet, dotpaths[i][1:]='%v' call to "+ // "HashGet returned '%s'\n", i, dotpaths[i][1:], ret.SexpString(nil)) if err != nil { return SexpNull, err } if i == lenpath-1 { return ret, nil } // invar: i < lenpath-1, so go deeper switch x := ret.(type) { case *SexpHash: //P("\n found hash in h2 at i=%d, looping to next i\n", i) askh = x case *Stack: return x.nestedPathGetSet(env, dotpaths[1:], setVal) // case *SexpReflect: // // at least allow reading, if we can. // P("hashutils DEBUG! SexpReflect value x is type: '%v', '%T'", x.Val.Type(), x.Val.Interface()) // return SexpNull, fmt.Errorf("not a record: cannot get field '%s'"+ // " out of type %T)", dotpaths[i+1][1:], x) default: return SexpNull, fmt.Errorf("not a record: cannot get field '%s'"+ " out of type %T)", dotpaths[i+1][1:], x) } } return ret, err } type ShortNamer interface { ShortName() string } func (hash *SexpHash) ShortName() string { return hash.TypeName } func (hash *SexpHash) SexpString(ps *PrintState) string { indInner := "" indent := ps.GetIndent() innerPs := ps.AddIndent(4) // generates a fresh new PrintState inner := indent + 4 prettyEnd := "" if hash.Env.Pretty { prettyEnd = "\n" indInner = strings.Repeat(" ", inner) } str := " (" + hash.TypeName + " " + prettyEnd for _, key := range hash.KeyOrder { val, err := hash.HashGet(hash.Env, key) if err == nil { switch s := key.(type) { case *SexpStr: str += indInner + s.S + ":" case *SexpSymbol: str += indInner + s.name + ":" default: str += indInner + key.SexpString(innerPs) + ":" } str += val.SexpString(innerPs) + " " + prettyEnd } else { // ignore deleted keys // don't panic(err) } } if len(hash.Map) > 0 { return str[:len(str)-1] + ")" + prettyEnd } return str + ")" + prettyEnd } func (r *SexpHash) Type() *RegisteredType { return GoStructRegistry.Registry[r.TypeName] } func compareHash(a *SexpHash, bs Sexp) (int, error) { var b *SexpHash switch bt := bs.(type) { case *SexpHash: b = bt default: return 0, fmt.Errorf("cannot compare %T to %T", a, bs) } if a.TypeName != b.TypeName { return 1, nil } return 0, nil } func (p *SexpHash) CopyMap() *map[int][]*SexpPair { cp := make(map[int][]*SexpPair) for k, v := range p.Map { cp[k] = v } return &cp } // CloneFrom copys all the internals of src into p, effectively // blanking out whatever p held and replacing it with a copy of src. func (p *SexpHash) CloneFrom(src *SexpHash) { p.TypeName = src.TypeName p.Map = *(src.CopyMap()) p.KeyOrder = src.KeyOrder p.GoStructFactory = src.GoStructFactory p.NumKeys = src.NumKeys p.GoMethods = src.GoMethods p.GoFields = src.GoFields p.GoMethSx = src.GoMethSx p.GoFieldSx = src.GoFieldSx p.GoType = src.GoType p.NumMethod = src.NumMethod p.GoShadowStruct = src.GoShadowStruct p.GoShadowStructVa = src.GoShadowStructVa p.ShadowSet = src.ShadowSet // json tag name -> pointers to example values, as factories for SexpToGoStructs() p.JsonTagMap = make(map[string]*HashFieldDet) for k, v := range src.JsonTagMap { p.JsonTagMap[k] = v } p.DetOrder = src.DetOrder // for using these as a scoping model p.DefnEnv = src.DefnEnv p.SuperClass = src.SuperClass p.ZMain = src.ZMain p.ZMethods = make(map[string]*SexpFunction) for k, v := range src.ZMethods { p.ZMethods[k] = v } p.Env = src.Env } func SetPrettyPrintFlag(env *Zlisp, name string, args []Sexp) (Sexp, error) { narg := len(args) if narg != 1 { return SexpNull, WrongNargs } b, isBool := args[0].(*SexpBool) if !isBool { return SexpNull, fmt.Errorf("argument to pretty must be a bool") } env.Pretty = b.Val return SexpNull, nil } // selectors for hash tables // SexpHashSelector: reference to a symbol in a hash table. type SexpHashSelector struct { Select Sexp Container *SexpHash } func (h *SexpHash) NewSexpHashSelector(sym *SexpSymbol) *SexpHashSelector { return &SexpHashSelector{ Select: sym, Container: h, } } func (si *SexpHashSelector) SexpString(ps *PrintState) string { rhs, err := si.RHS(si.Container.Env) if err != nil { return fmt.Sprintf("SexpHashSelector error: could not get RHS: '%v'", err) } return fmt.Sprintf("%v /*(hashSelector %v %v)*/", rhs.SexpString(ps), si.Container.SexpString(ps), si.Select.SexpString(ps)) } // Type returns the type of the value. func (si *SexpHashSelector) Type() *RegisteredType { return GoStructRegistry.Lookup("hashSelector") } // RHS applies the selector to the contain and returns // the value obtained. func (x *SexpHashSelector) RHS(env *Zlisp) (sx Sexp, err error) { if env == nil { panic("SexpHashSelector.RSH() called with nil env") } if x.Select == nil { panic("cannot call RHS on hash selector with nil Select") } Q("SexpHashSelector.RHS(): x.Select is '%#v'", x.Select) switch t := x.Select.(type) { case *SexpSymbol: Q("SexpHashSelector.RHS(): x.Select is symbol, t = '%#v'", t) Q("SexpHashSelector.RHS(): x.Container is '%v'", x.Container.SexpString(nil)) sx, err = x.Container.DotPathHashGet(x.Container.Env, t) if err != nil { Q("SexpHashSelector.RHS() sees err when calling"+ " on x.Container.DotPathHashGet: with query t='%#v' err='%v'", t, err) return SexpNull, err } default: Q("SexpHashSelector.RHS() selector is not a symbol, x= '%#v'", x) sx, err = x.Container.HashGet(x.Container.Env, x.Select) if err != nil { Q("SexpHashSelector.RHS() sees err when calling"+ " on x.Container.HashGet: '%v'", err) return SexpNull, err } //return &SexpStr{S: fmt.Sprintf("(hashidx %s %s)", x.Container.SexpString(nil), x.Select.SexpString(nil))}, nil } Q("SexpHashSelector) RHS() returning sx = '%v'", sx) return sx, nil } func (x *SexpHashSelector) AssignToSelection(env *Zlisp, rhs Sexp) error { Q("in SexpHashSelector.AssignToSelection with rhs = '%v' and container = '%v'", rhs.SexpString(nil), x.Container.SexpString(nil)) switch sym := x.Select.(type) { case *SexpSymbol: path := DotPartsRegex.FindAllString(sym.name, -1) // leave dots in path, they are expected. _, err := x.Container.nestedPathGetSet(env, path, &rhs) return err } return x.Container.HashSet(x.Select, rhs) } // (arrayidx ar [0 1]) refers here func HashIndexFunction(env *Zlisp, name string, args []Sexp) (Sexp, error) { Q("in HashIndexFunction, with %v args = '%#v', env=%p", len(args), args, env) for i := range args { Q("in HashIndexFunction, args[%v] = '%v'", i, args[i].SexpString(nil)) } narg := len(args) if narg != 2 { return SexpNull, WrongNargs } tmp, err := env.ResolveDotSym([]Sexp{args[0]}) if err != nil { return SexpNull, err } args[0] = tmp[0] Q("HashIndexFunction: past dot resolve, args[0] is now type %T/val='%v'", args[0], args[0].SexpString(nil)) var hash *SexpHash switch ar0 := args[0].(type) { case *SexpHash: hash = ar0 case *SexpArray: Q("HashIndexFunction: args[0] is an array, defering to ArrayIndexFunction") return ArrayIndexFunction(env, name, args) case Selector: x, err := ar0.RHS(env) Q("ar0.RHS() returned x = %#v", x) if err != nil { Q("HashIndexFunction: Selector error: '%v'", err) return SexpNull, err } switch xH := x.(type) { case *SexpHash: hash = xH case *SexpHashSelector: x, err := xH.RHS(env) if err != nil { Q("HashIndexFunction: hash retreival from "+ "SexpHashSelector gave error: '%v'", err) return SexpNull, err } switch xHash2 := x.(type) { case *SexpHash: hash = xHash2 default: return SexpNull, fmt.Errorf("bad (hashidx h2 index) call: h2 was a hashidx itself, but it did not resolve to an hash, instead '%s'/type %T", x.SexpString(nil), x) } case *SexpArray: Q("HashIndexFunction sees args[0] is Selector"+ " that resolved to an array '%v'", xH.SexpString(nil)) return ArrayIndexFunction(env, name, []Sexp{xH, args[1]}) default: return SexpNull, fmt.Errorf("bad (hashidx h index) call: h did not resolve to a hash, instead '%s'/type %T", x.SexpString(nil), x) // failing here with x a *SexpStr } default: return SexpNull, fmt.Errorf("bad (hashidx h index) call: h was not a hashmap, instead '%s'/type %T", args[0].SexpString(nil), args[0]) } sel := args[1] switch x := sel.(type) { case *SexpSymbol: sel = x /* if x.isDot { Q("hashidx sees dot symbol: '%s', removing any prefix dot", x.name) if len(x.name) >= 2 && x.name[0] == '.' { selSym := env.MakeSymbol(x.name[1:]) //selSym.isDot = true sel = selSym } } */ default: // okay to have SexpArray/other as selector } ret := SexpHashSelector{ Select: sel, Container: hash, } Q("HashIndexFunction: returning without error, ret.Select = '%v'", args[1].SexpString(nil)) return &ret, nil } func (env *Zlisp) EliminateColonAndCommaFromArgs(args []Sexp) []Sexp { r := []Sexp{} outerLoop: for i := range args { switch x := args[i].(type) { case *SexpComma: //Q("eliminating comma") continue outerLoop case *SexpFunction: if x.name == ":" { //Q("eliminating ColonFunc: args[%d] = %T/val=%#v", i, x, x) continue outerLoop } } r = append(r, args[i]) } return r }