package parse import ( "errors" "fmt" "reflect" "strconv" "sectorinf.com/emilis/flabk/pkg/coll" "sectorinf.com/emilis/flabk/pkg/ld/internal/consts" "sectorinf.com/emilis/flabk/pkg/ld/internal/parse/chunk" ) const ( memberSeparator = ',' ) var ( ErrNotArray = errors.New("value is not an array") ErrNotMap = errors.New("value is not a map") ) var ( boolVals = map[string]bool{ "true": true, "false": false, } determineMap = func() map[byte]coll.Vector[reflect.Kind] { m := map[byte]coll.Vector[reflect.Kind]{ '"': coll.From(reflect.String), '[': coll.From(reflect.Array, reflect.Slice), '{': coll.From(reflect.Map, reflect.Struct), '-': coll.From(reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64), 't': coll.From(reflect.Bool), 'f': coll.From(reflect.Bool), } numbers := coll.From( reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint64, reflect.Uint32, reflect.Uint16, reflect.Uint8, reflect.Float32, reflect.Float64, ) for index := byte('0'); index <= '9'; index++ { m[index] = numbers } return m }() sizes = map[reflect.Kind]int{ // Int and Uint values stolen from the [math] package, as the intSize // constant is unexported reflect.Int: 32 << (^uint(0) >> 63), // 32 or 64 reflect.Uint: 32 << (^uint(0) >> 63), // 32 or 64 reflect.Int64: 64, reflect.Int32: 32, reflect.Int16: 16, reflect.Int8: 8, reflect.Uint64: 64, reflect.Uint32: 32, reflect.Uint16: 16, reflect.Uint8: 8, reflect.Float32: 32, reflect.Float64: 64, } ) func SetMap(val reflect.Value, typ reflect.Type, c chunk.Chunk) error { if c.LeftByte() != '{' { return c.LeftPosf("%w", ErrNotMap) } fields := GetStructFields(val, typ) c = c.CookieCutter().Seek() for !c.EOF() { row, err := c.Row() if err != nil { return fmt.Errorf("row: %w", err) } field, ok := fields[row.Name] if ok { if err := SetValue(field, field.Type(), row.Value); err != nil { return fmt.Errorf("set: %w", err) } } // Shift c c = c.After(row.Value).StepIf(memberSeparator).Seek() } return nil } func SetArray(val reflect.Value, typ reflect.Type, c chunk.Chunk) error { if c.LeftByte() != '[' { return c.LeftPosf("%w", ErrNotArray) } c = c.CookieCutter().Seek() elems := coll.New[chunk.Chunk]() for !c.EOF() { element, err := c.ValueEnd() if err != nil { return fmt.Errorf("getting array elem: %w", err) } elems = elems.Push(element) c = c.After(element).StepIf(memberSeparator).Seek() } elementType := typ.Elem() arrayType := reflect.ArrayOf(len(elems), elementType) array := reflect.New(arrayType).Elem() for index, elem := range elems { if err := SetValue(array.Index(index), elementType, elem); err != nil { return fmt.Errorf("%w at array index [%d]", err, index) } } if val.Kind() == reflect.Slice { array = array.Slice(0, len(elems)) } val.Set(array) return nil } // Determines what [reflect.Kind] of object the given [chunk.Chunk] is func Determine(c chunk.Chunk) coll.Vector[reflect.Kind] { k, ok := determineMap[c.LeftByte()] if !ok { return coll.From(reflect.Invalid) } return k } func SetValue(val reflect.Value, typ reflect.Type, c chunk.Chunk) error { if c.Null() { return nil } if val.Kind() == reflect.Pointer { if val.IsNil() { val.Set(reflect.New(typ.Elem())) } return SetValue(val.Elem(), typ.Elem(), c) } kinds := Determine(c) if !coll.AnyOf(kinds, val.Kind()) { return fmt.Errorf("kind %s not found in appropriate list %s for value [%s]", val.Kind(), kinds, c) } switch val.Kind() { case reflect.Map, reflect.Struct: return SetMap(val, typ, c) case reflect.String: val.SetString(Escape(c.CookieCutter())) case reflect.Bool: b, ok := boolVals[c.String()] if !ok { return fmt.Errorf("value [%s] is not a boolean", c.String()) } val.SetBool(b) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: i, err := strconv.ParseInt(c.String(), 10, sizes[val.Kind()]) if err != nil { return fmt.Errorf("value [%s] is not an %s", c.String(), val.Kind().String()) } val.SetInt(i) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: u, err := strconv.ParseUint(c.String(), 10, sizes[val.Kind()]) if err != nil { return fmt.Errorf("value [%s] is not an %s", c.String(), val.Kind().String()) } val.SetUint(u) case reflect.Float32, reflect.Float64: f, err := strconv.ParseFloat(c.String(), sizes[val.Kind()]) if err != nil { return fmt.Errorf("value [%s] is not an %s", c.String(), val.Kind().String()) } val.SetFloat(f) case reflect.Array, reflect.Slice: return SetArray(val, typ, c) default: fmt.Println(val.Kind().String()) panic("unsupported") } return nil } func Escape(v chunk.Chunk) string { // TODO: string escaping return v.String() } func GetStructFields(val reflect.Value, typ reflect.Type) map[string]reflect.Value { total := val.NumField() out := map[string]reflect.Value{} for index := 0; index < total; index++ { cName := StructName(typ.Field(index)) if cName != "-" { out[cName] = val.Field(index) } } return out } func StructName(v reflect.StructField) string { tag := v.Tag.Get(consts.PkgTag) if tag != "" { return tag } // Default to field name return v.Name }