14 // struc:"int32,big,sizeof=Data"
16 var tagWordsRe = regexp.MustCompile(`(\[|\b)[^"]+\b+$`)
18 type strucTag struct {
20 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 s == "big" {
40 t.Order = binary.BigEndian
41 } else if s == "little" {
42 t.Order = binary.LittleEndian
50 var typeLenRe = regexp.MustCompile(`^\[(\d*)\]`)
52 func parseField(f reflect.StructField) (fd *Field, err error) {
53 tag := parseStrucTag(f.Tag)
67 fd.kind = f.Type.Elem().Kind()
71 fd.kind = f.Type.Elem().Kind()
74 fd.kind = f.Type.Elem().Kind()
76 // check for custom types
77 tmp := reflect.New(f.Type)
78 if _, ok := tmp.Interface().(Custom); ok {
83 fd.defType, defTypeOk = reflectTypeMap[fd.kind]
84 // find a type in the struct tag
85 pureType := typeLenRe.ReplaceAllLiteralString(tag.Type, "")
86 if fd.Type, ok = typeLookup[pureType]; ok {
88 match := typeLenRe.FindAllStringSubmatch(tag.Type, -1)
89 if len(match) > 0 && len(match[0]) > 1 {
92 // Field.Len = -1 indicates a []slice
96 fd.Len, err = strconv.Atoi(first)
101 // the user didn't specify a type
103 case reflect.TypeOf(Size_t(0)):
105 case reflect.TypeOf(Off_t(0)):
111 err = errors.New("struc: Could not find field type.")
117 func parseFieldsLocked(v reflect.Value) (Fields, error) {
118 // we need to repeat this logic because parseFields() below can't be recursively called due to locking
119 for v.Kind() == reflect.Ptr {
123 if v.NumField() < 1 {
124 return nil, errors.New("struc: Struct has no fields.")
126 sizeofMap := make(map[string][]int)
127 fields := make(Fields, 0, v.NumField())
128 for i := 0; i < t.NumField(); i++ {
130 f, err := parseField(field)
134 f.CanSet = v.Field(i).CanSet()
139 tag := parseStrucTag(field.Tag)
140 if tag.Sizeof != "" {
141 target, ok := t.FieldByName(tag.Sizeof)
143 return nil, fmt.Errorf("struc: `sizeof=%s` field does not exist", tag.Sizeof)
145 f.Sizeof = target.Index
146 sizeofMap[tag.Sizeof] = field.Index
148 if sizefrom, ok := sizeofMap[field.Name]; ok {
149 f.Sizefrom = sizefrom
151 if f.Len == -1 && f.Sizefrom == nil {
152 return nil, fmt.Errorf("struc: field `%s` is a slice with no length or sizeof field", field.Name)
154 // recurse into nested structs
155 // TODO: handle loops (probably by indirecting the []Field and putting pointer in cache)
156 if f.Type == Struct {
164 f.Fields, err = parseFieldsLocked(reflect.New(typ))
169 fields = append(fields, f)
174 var fieldCache = make(map[reflect.Type]Fields)
175 var fieldCacheLock sync.RWMutex
176 var parseLock sync.Mutex
178 func fieldCacheLookup(t reflect.Type) Fields {
179 fieldCacheLock.RLock()
180 defer fieldCacheLock.RUnlock()
181 if cached, ok := fieldCache[t]; ok {
187 func parseFields(v reflect.Value) (Fields, error) {
188 for v.Kind() == reflect.Ptr {
193 // fast path: hopefully the field parsing is already cached
194 if cached := fieldCacheLookup(t); cached != nil {
198 // hold a global lock so multiple goroutines can't parse (the same) fields at once
200 defer parseLock.Unlock()
202 // check cache a second time, in case parseLock was just released by
203 // another thread who filled the cache for us
204 if cached := fieldCacheLookup(t); cached != nil {
208 // no luck, time to parse and fill the cache ourselves
209 fields, err := parseFieldsLocked(v)
213 fieldCacheLock.Lock()
214 fieldCache[t] = fields
215 fieldCacheLock.Unlock()