14 // struc:"int32,big,sizeof=Data,skip,sizefrom=Len"
16 type strucTag struct {
18 Order binary.ByteOrder
24 func parseStrucTag(tag reflect.StructTag) *strucTag {
26 Order: binary.BigEndian,
28 tagStr := tag.Get("struc")
30 // someone's going to typo this (I already did once)
31 // sorry if you made a module actually using this tag
32 // and you're mad at me now
33 tagStr = tag.Get("struct")
35 for _, s := range strings.Split(tagStr, ",") {
36 if strings.HasPrefix(s, "sizeof=") {
37 tmp := strings.SplitN(s, "=", 2)
39 } else if strings.HasPrefix(s, "sizefrom=") {
40 tmp := strings.SplitN(s, "=", 2)
42 } else if s == "big" {
43 t.Order = binary.BigEndian
44 } else if s == "little" {
45 t.Order = binary.LittleEndian
46 } else if s == "skip" {
55 var typeLenRe = regexp.MustCompile(`^\[(\d*)\]`)
57 func parseField(f reflect.StructField) (fd *Field, tag *strucTag, err error) {
58 tag = parseStrucTag(f.Tag)
72 fd.kind = f.Type.Elem().Kind()
76 fd.kind = f.Type.Elem().Kind()
79 fd.kind = f.Type.Elem().Kind()
81 // check for custom types
82 tmp := reflect.New(f.Type)
83 if _, ok := tmp.Interface().(Custom); ok {
88 fd.defType, defTypeOk = reflectTypeMap[fd.kind]
89 // find a type in the struct tag
90 pureType := typeLenRe.ReplaceAllLiteralString(tag.Type, "")
91 if fd.Type, ok = typeLookup[pureType]; ok {
93 match := typeLenRe.FindAllStringSubmatch(tag.Type, -1)
94 if len(match) > 0 && len(match[0]) > 1 {
97 // Field.Len = -1 indicates a []slice
101 fd.Len, err = strconv.Atoi(first)
106 // the user didn't specify a type
108 case reflect.TypeOf(Size_t(0)):
110 case reflect.TypeOf(Off_t(0)):
116 err = errors.New("struc: Could not find field type.")
122 func parseFieldsLocked(v reflect.Value) (Fields, error) {
123 // we need to repeat this logic because parseFields() below can't be recursively called due to locking
124 for v.Kind() == reflect.Ptr {
128 if v.NumField() < 1 {
129 return nil, errors.New("struc: Struct has no fields.")
131 sizeofMap := make(map[string][]int)
132 fields := make(Fields, v.NumField())
133 for i := 0; i < t.NumField(); i++ {
135 f, tag, err := parseField(field)
142 if !v.Field(i).CanSet() {
146 if tag.Sizeof != "" {
147 target, ok := t.FieldByName(tag.Sizeof)
149 return nil, fmt.Errorf("struc: `sizeof=%s` field does not exist", tag.Sizeof)
151 f.Sizeof = target.Index
152 sizeofMap[tag.Sizeof] = field.Index
154 if sizefrom, ok := sizeofMap[field.Name]; ok {
155 f.Sizefrom = sizefrom
157 if tag.Sizefrom != "" {
158 source, ok := t.FieldByName(tag.Sizefrom)
160 return nil, fmt.Errorf("struc: `sizefrom=%s` field does not exist", tag.Sizefrom)
162 f.Sizefrom = source.Index
164 if f.Len == -1 && f.Sizefrom == nil {
165 return nil, fmt.Errorf("struc: field `%s` is a slice with no length or sizeof field", field.Name)
167 // recurse into nested structs
168 // TODO: handle loops (probably by indirecting the []Field and putting pointer in cache)
169 if f.Type == Struct {
177 f.Fields, err = parseFieldsLocked(reflect.New(typ))
187 var fieldCache = make(map[reflect.Type]Fields)
188 var fieldCacheLock sync.RWMutex
189 var parseLock sync.Mutex
191 func fieldCacheLookup(t reflect.Type) Fields {
192 fieldCacheLock.RLock()
193 defer fieldCacheLock.RUnlock()
194 if cached, ok := fieldCache[t]; ok {
200 func parseFields(v reflect.Value) (Fields, error) {
201 for v.Kind() == reflect.Ptr {
206 // fast path: hopefully the field parsing is already cached
207 if cached := fieldCacheLookup(t); cached != nil {
211 // hold a global lock so multiple goroutines can't parse (the same) fields at once
213 defer parseLock.Unlock()
215 // check cache a second time, in case parseLock was just released by
216 // another thread who filled the cache for us
217 if cached := fieldCacheLookup(t); cached != nil {
221 // no luck, time to parse and fill the cache ourselves
222 fields, err := parseFieldsLocked(v)
226 fieldCacheLock.Lock()
227 fieldCache[t] = fields
228 fieldCacheLock.Unlock()