2 Star 2 Fork 7

王布衣 / gox

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
parse.go 5.24 KB
AI 代码解读
一键复制 编辑 原始数据 按行查看 历史
package struc
import (
"encoding/binary"
"errors"
"fmt"
"reflect"
"regexp"
"strconv"
"strings"
"sync"
)
// struc:"int32,big,sizeof=Data,skip,sizefrom=Len"
type strucTag struct {
Type string
Order binary.ByteOrder
Sizeof string
Skip bool
Sizefrom string
}
func parseStrucTag(tag reflect.StructTag) *strucTag {
t := &strucTag{
Order: binary.BigEndian,
}
tagStr := tag.Get("struc")
if tagStr == "" {
// someone's going to typo this (I already did once)
// sorry if you made a module actually using this tag
// and you're mad at me now
tagStr = tag.Get("struct")
}
for _, s := range strings.Split(tagStr, ",") {
if strings.HasPrefix(s, "sizeof=") {
tmp := strings.SplitN(s, "=", 2)
t.Sizeof = tmp[1]
} else if strings.HasPrefix(s, "sizefrom=") {
tmp := strings.SplitN(s, "=", 2)
t.Sizefrom = tmp[1]
} else if s == "big" {
t.Order = binary.BigEndian
} else if s == "little" {
t.Order = binary.LittleEndian
} else if s == "skip" {
t.Skip = true
} else {
t.Type = s
}
}
return t
}
var typeLenRe = regexp.MustCompile(`^\[(\d*)\]`)
func parseField(f reflect.StructField) (fd *Field, tag *strucTag, err error) {
tag = parseStrucTag(f.Tag)
var ok bool
fd = &Field{
Name: f.Name,
Len: 1,
Order: tag.Order,
Slice: false,
kind: f.Type.Kind(),
}
switch fd.kind {
case reflect.Array:
fd.Slice = true
fd.Array = true
fd.Len = f.Type.Len()
fd.kind = f.Type.Elem().Kind()
case reflect.Slice:
fd.Slice = true
fd.Len = -1
fd.kind = f.Type.Elem().Kind()
case reflect.Ptr:
fd.Ptr = true
fd.kind = f.Type.Elem().Kind()
}
// check for custom types
tmp := reflect.New(f.Type)
if _, ok := tmp.Interface().(Custom); ok {
fd.Type = CustomType
return
}
var defTypeOk bool
fd.defType, defTypeOk = reflectTypeMap[fd.kind]
// find a type in the struct tag
pureType := typeLenRe.ReplaceAllLiteralString(tag.Type, "")
if fd.Type, ok = typeLookup[pureType]; ok {
fd.Len = 1
match := typeLenRe.FindAllStringSubmatch(tag.Type, -1)
if len(match) > 0 && len(match[0]) > 1 {
fd.Slice = true
first := match[0][1]
// Field.Len = -1 indicates a []slice
if first == "" {
fd.Len = -1
} else {
fd.Len, err = strconv.Atoi(first)
}
}
return
}
// the user didn't specify a type
switch f.Type {
case reflect.TypeOf(Size_t(0)):
fd.Type = SizeType
case reflect.TypeOf(Off_t(0)):
fd.Type = OffType
default:
if defTypeOk {
fd.Type = fd.defType
} else {
err = errors.New(fmt.Sprintf("struc: Could not resolve field '%v' type '%v'.", f.Name, f.Type))
}
}
return
}
func parseFieldsLocked(v reflect.Value) (Fields, error) {
// we need to repeat this logic because parseFields() below can't be recursively called due to locking
for v.Kind() == reflect.Ptr {
v = v.Elem()
}
t := v.Type()
if v.NumField() < 1 {
return nil, errors.New("struc: Struct has no fields.")
}
sizeofMap := make(map[string][]int)
fields := make(Fields, v.NumField())
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
f, tag, err := parseField(field)
if tag.Skip {
continue
}
if err != nil {
return nil, err
}
if !v.Field(i).CanSet() {
continue
}
f.Index = i
if tag.Sizeof != "" {
target, ok := t.FieldByName(tag.Sizeof)
if !ok {
return nil, fmt.Errorf("struc: `sizeof=%s` field does not exist", tag.Sizeof)
}
f.Sizeof = target.Index
sizeofMap[tag.Sizeof] = field.Index
}
if sizefrom, ok := sizeofMap[field.Name]; ok {
f.Sizefrom = sizefrom
}
if tag.Sizefrom != "" {
source, ok := t.FieldByName(tag.Sizefrom)
if !ok {
return nil, fmt.Errorf("struc: `sizefrom=%s` field does not exist", tag.Sizefrom)
}
f.Sizefrom = source.Index
}
if f.Len == -1 && f.Sizefrom == nil {
return nil, fmt.Errorf("struc: field `%s` is a slice with no length or sizeof field", field.Name)
}
// recurse into nested structs
// TODO: handle loops (probably by indirecting the []Field and putting pointer in cache)
if f.Type == Struct {
typ := field.Type
if f.Ptr {
typ = typ.Elem()
}
if f.Slice {
typ = typ.Elem()
}
f.Fields, err = parseFieldsLocked(reflect.New(typ))
if err != nil {
return nil, err
}
}
fields[i] = f
}
return fields, nil
}
var fieldCache = make(map[reflect.Type]Fields)
var fieldCacheLock sync.RWMutex
var parseLock sync.Mutex
func fieldCacheLookup(t reflect.Type) Fields {
fieldCacheLock.RLock()
defer fieldCacheLock.RUnlock()
if cached, ok := fieldCache[t]; ok {
return cached
}
return nil
}
func parseFields(v reflect.Value) (Fields, error) {
for v.Kind() == reflect.Ptr {
v = v.Elem()
}
t := v.Type()
// fast path: hopefully the field parsing is already cached
if cached := fieldCacheLookup(t); cached != nil {
return cached, nil
}
// hold a global lock so multiple goroutines can't parse (the same) fields at once
parseLock.Lock()
defer parseLock.Unlock()
// check cache a second time, in case parseLock was just released by
// another thread who filled the cache for us
if cached := fieldCacheLookup(t); cached != nil {
return cached, nil
}
// no luck, time to parse and fill the cache ourselves
fields, err := parseFieldsLocked(v)
if err != nil {
return nil, err
}
fieldCacheLock.Lock()
fieldCache[t] = fields
fieldCacheLock.Unlock()
return fields, nil
}
Go
1
https://gitee.com/quant1x/gox.git
git@gitee.com:quant1x/gox.git
quant1x
gox
gox
v1.21.2

搜索帮助