14 // struc:"int32,big,sizeof=Data"
16 type strucTag struct {
18 Order binary.ByteOrder
23 func parseStrucTag(tag reflect.StructTag) *strucTag {
25 Order: binary.BigEndian,
27 tagStr := tag.Get("struc")
29 // someone's going to typo this (I already did once)
30 // sorry if you made a module actually using this tag
31 // and you're mad at me now
32 tagStr = tag.Get("struct")
34 for _, s := range strings.Split(tagStr, ",") {
35 if strings.HasPrefix(s, "sizeof=") {
36 tmp := strings.SplitN(s, "=", 2)
38 } else if s == "big" {
39 t.Order = binary.BigEndian
40 } else if s == "little" {
41 t.Order = binary.LittleEndian
42 } else if s == "skip" {
51 var typeLenRe = regexp.MustCompile(`^\[(\d*)\]`)
53 func parseField(f reflect.StructField) (fd *Field, tag *strucTag, err error) {
54 tag = parseStrucTag(f.Tag)
68 fd.kind = f.Type.Elem().Kind()
72 fd.kind = f.Type.Elem().Kind()
75 fd.kind = f.Type.Elem().Kind()
77 // check for custom types
78 tmp := reflect.New(f.Type)
79 if _, ok := tmp.Interface().(Custom); ok {
84 fd.defType, defTypeOk = reflectTypeMap[fd.kind]
85 // find a type in the struct tag
86 pureType := typeLenRe.ReplaceAllLiteralString(tag.Type, "")
87 if fd.Type, ok = typeLookup[pureType]; ok {
89 match := typeLenRe.FindAllStringSubmatch(tag.Type, -1)
90 if len(match) > 0 && len(match[0]) > 1 {
93 // Field.Len = -1 indicates a []slice
97 fd.Len, err = strconv.Atoi(first)
102 // the user didn't specify a type
104 case reflect.TypeOf(Size_t(0)):
106 case reflect.TypeOf(Off_t(0)):
112 err = errors.New("struc: Could not find field type.")
118 func parseFieldsLocked(v reflect.Value) (Fields, error) {
119 // we need to repeat this logic because parseFields() below can't be recursively called due to locking
120 for v.Kind() == reflect.Ptr {
124 if v.NumField() < 1 {
125 return nil, errors.New("struc: Struct has no fields.")
127 sizeofMap := make(map[string][]int)
128 fields := make(Fields, v.NumField())
129 for i := 0; i < t.NumField(); i++ {
131 f, tag, err := parseField(field)
138 if !v.Field(i).CanSet() {
142 if tag.Sizeof != "" {
143 target, ok := t.FieldByName(tag.Sizeof)
145 return nil, fmt.Errorf("struc: `sizeof=%s` field does not exist", tag.Sizeof)
147 f.Sizeof = target.Index
148 sizeofMap[tag.Sizeof] = field.Index
150 if sizefrom, ok := sizeofMap[field.Name]; ok {
151 f.Sizefrom = sizefrom
153 if f.Len == -1 && f.Sizefrom == nil {
154 return nil, fmt.Errorf("struc: field `%s` is a slice with no length or sizeof field", field.Name)
156 // recurse into nested structs
157 // TODO: handle loops (probably by indirecting the []Field and putting pointer in cache)
158 if f.Type == Struct {
166 f.Fields, err = parseFieldsLocked(reflect.New(typ))
176 var fieldCache = make(map[reflect.Type]Fields)
177 var fieldCacheLock sync.RWMutex
178 var parseLock sync.Mutex
180 func fieldCacheLookup(t reflect.Type) Fields {
181 fieldCacheLock.RLock()
182 defer fieldCacheLock.RUnlock()
183 if cached, ok := fieldCache[t]; ok {
189 func parseFields(v reflect.Value) (Fields, error) {
190 for v.Kind() == reflect.Ptr {
195 // fast path: hopefully the field parsing is already cached
196 if cached := fieldCacheLookup(t); cached != nil {
200 // hold a global lock so multiple goroutines can't parse (the same) fields at once
202 defer parseLock.Unlock()
204 // check cache a second time, in case parseLock was just released by
205 // another thread who filled the cache for us
206 if cached := fieldCacheLookup(t); cached != nil {
210 // no luck, time to parse and fill the cache ourselves
211 fields, err := parseFieldsLocked(v)
215 fieldCacheLock.Lock()
216 fieldCache[t] = fields
217 fieldCacheLock.Unlock()