func (r *reader) computeMethodSets() {
	for _, t := range r.types {
		if t.isStruct {
			r.collectEmbeddedMethods(t.methods, t, t.name, false, 1, make(embeddedSet))
		} else {
		}
	}
	if r.errorDecl {
		for _, ityp := range r.fixlist {
			removeErrorField(ityp)
		}
	}
}
computeMethodSets determines the actual method sets for each type encountered.
package doc
import (
	"regexp"
	"sort"
	"strconv"
	"github.com/tcard/sgo/sgo/ast"
	"github.com/tcard/sgo/sgo/token"
)
type methodSet map[string]*Func
func recvString(recv ast.Expr) string {
	switch t := recv.(type) {
	case *ast.Ident:
		return t.Name
	case *ast.StarExpr:
		return "*" + recvString(t.X)
	}
	return "BADRECV"
}
func (mset methodSet) set(f *ast.FuncDecl) {
	name := f.Name.Name
	if g := mset[name]; g != nil && g.Doc != "" {
		return
	}
	recv := ""
	if f.Recv != nil {
		var typ ast.Expr
		if list := f.Recv.List; len(list) == 1 {
			typ = list[0].Type
		}
		recv = recvString(typ)
	}
	mset[name] = &Func{
		Doc:  f.Doc.Text(),
		Name: name,
		Decl: f,
		Recv: recv,
		Orig: recv,
	}
	f.Doc = nil  
}
func (mset methodSet) add(m *Func) {
	old := mset[m.Name]
	if old == nil || m.Level < old.Level {
		mset[m.Name] = m
		return
	}
	if old != nil && m.Level == old.Level {
		mset[m.Name] = &Func{
			Name:  m.Name,
			Level: m.Level,
		}
	}
}
func baseTypeName(x ast.Expr) (name string, imported bool) {
	switch t := x.(type) {
	case *ast.Ident:
		return t.Name, false
	case *ast.SelectorExpr:
		if _, ok := t.X.(*ast.Ident); ok {
			return t.Sel.Name, true
		}
	case *ast.StarExpr:
		return baseTypeName(t.X)
	}
	return
}
type embeddedSet map[*namedType]bool
type namedType struct {
	doc  string        
	name string        
	decl *ast.GenDecl  
	isEmbedded bool         
	isStruct   bool         
	embedded   embeddedSet  
	values  []*Value  
	funcs   methodSet
	methods methodSet
}
type reader struct {
	mode Mode
	doc       string  
	filenames []string
	notes     map[string][]*Note
	imports   map[string]int
	hasDotImp bool      
	values    []*Value  
	order     int       
	types     map[string]*namedType
	funcs     methodSet
	errorDecl bool                  
	fixlist   []*ast.InterfaceType  
}
func (r *reader) isVisible(name string) bool {
	return r.mode&AllDecls != 0 || ast.IsExported(name)
}
func (r *reader) lookupType(name string) *namedType {
	if name == "" || name == "_" {
		return nil  
	}
	if typ, found := r.types[name]; found {
		return typ
	}
	typ := &namedType{
		name:     name,
		embedded: make(embeddedSet),
		funcs:    make(methodSet),
		methods:  make(methodSet),
	}
	r.types[name] = typ
	return typ
}
func (r *reader) recordAnonymousField(parent *namedType, fieldType ast.Expr) (fname string) {
	fname, imp := baseTypeName(fieldType)
	if parent == nil || imp {
		return
	}
	if ftype := r.lookupType(fname); ftype != nil {
		ftype.isEmbedded = true
		_, ptr := fieldType.(*ast.StarExpr)
		parent.embedded[ftype] = ptr
	}
	return
}
func (r *reader) readDoc(comment *ast.CommentGroup) {
	text := comment.Text()
	if r.doc == "" {
		r.doc = text
		return
	}
	r.doc += "\n" + text
}
func (r *reader) remember(typ *ast.InterfaceType) {
	r.fixlist = append(r.fixlist, typ)
}
func specNames(specs []ast.Spec) []string {
	names := make([]string, 0, len(specs))  
	for _, s := range specs {
		for _, ident := range s.(*ast.ValueSpec).Names.List {
			names = append(names, ident.Name)
		}
	}
	return names
}
func (r *reader) readValue(decl *ast.GenDecl) {
	domName := ""
	domFreq := 0
	prev := ""
	n := 0
	for _, spec := range decl.Specs {
		s, ok := spec.(*ast.ValueSpec)
		if !ok {
			continue  
		}
		name := ""
		switch {
		case s.Type != nil:
			if n, imp := baseTypeName(s.Type); !imp {
				name = n
			}
		case decl.Tok == token.CONST && s.Values.Len() == 0:
			name = prev
		}
		if name != "" {
			if domName != "" && domName != name {
				domName = ""
				break
			}
			domName = name
			domFreq++
		}
		prev = name
		n++
	}
	if n == 0 {
		return
	}
	values := &r.values
	const threshold = 0.75
	if domName != "" && r.isVisible(domName) && domFreq >= int(float64(len(decl.Specs))*threshold) {
		if typ := r.lookupType(domName); typ != nil {
			values = &typ.values  
		}
	}
	*values = append(*values, &Value{
		Doc:   decl.Doc.Text(),
		Names: specNames(decl.Specs),
		Decl:  decl,
		order: r.order,
	})
	decl.Doc = nil  
	r.order++
}
func fields(typ ast.Expr) (list []*ast.Field, isStruct bool) {
	var fields *ast.FieldList
	switch t := typ.(type) {
	case *ast.StructType:
		fields = t.Fields
		isStruct = true
	case *ast.InterfaceType:
		fields = t.Methods
	}
	if fields != nil {
		list = fields.List
	}
	return
}
func (r *reader) readType(decl *ast.GenDecl, spec *ast.TypeSpec) {
	typ := r.lookupType(spec.Name.Name)
	if typ == nil {
		return  
	}
	typ.decl = decl
	doc := spec.Doc
	spec.Doc = nil  
	if doc == nil {
		doc = decl.Doc
	}
	decl.Doc = nil  
	typ.doc = doc.Text()
	var list []*ast.Field
	list, typ.isStruct = fields(spec.Type)
	for _, field := range list {
		if len(field.Names) == 0 {
			r.recordAnonymousField(typ, field.Type)
		}
	}
}
func (r *reader) readFunc(fun *ast.FuncDecl) {
	fun.Body = nil
	if fun.Recv != nil {
		if len(fun.Recv.List) == 0 {
			return
		}
		recvTypeName, imp := baseTypeName(fun.Recv.List[0].Type)
		if imp {
			return
		}
		if typ := r.lookupType(recvTypeName); typ != nil {
			typ.methods.set(fun)
		}
		return
	}
	if fun.Type.Results.NumFields() >= 1 {
		res := fun.Type.Results.List[0]
		if len(res.Names) <= 1 {
			factoryType := res.Type
			if t, ok := factoryType.(*ast.ArrayType); ok && t.Len == nil {
				factoryType = t.Elt
			}
			if n, imp := baseTypeName(factoryType); !imp && r.isVisible(n) {
				if typ := r.lookupType(n); typ != nil {
					typ.funcs.set(fun)
					return
				}
			}
		}
	}
	r.funcs.set(fun)
}
var (
	noteMarker    = `([A-Z][A-Z]+)\(([^)]+)\):?`                     
	noteMarkerRx  = regexp.MustCompile(`^[ \t]*` + noteMarker)       
	noteCommentRx = regexp.MustCompile(`^/[/*][ \t]*` + noteMarker)  
)
func (r *reader) readNote(list []*ast.Comment) {
	text := (&ast.CommentGroup{List: list}).Text()
	if m := noteMarkerRx.FindStringSubmatchIndex(text); m != nil {
		body := clean(text[m[1]:], keepNL)
		if body != "" {
			marker := text[m[2]:m[3]]
			r.notes[marker] = append(r.notes[marker], &Note{
				Pos:  list[0].Pos(),
				End:  list[len(list)-1].End(),
				UID:  text[m[4]:m[5]],
				Body: body,
			})
		}
	}
}
func (r *reader) readNotes(comments []*ast.CommentGroup) {
	for _, group := range comments {
		i := -1  
		list := group.List
		for j, c := range list {
			if noteCommentRx.MatchString(c.Text) {
				if i >= 0 {
					r.readNote(list[i:j])
				}
				i = j
			}
		}
		if i >= 0 {
			r.readNote(list[i:])
		}
	}
}
func (r *reader) readFile(src *ast.File) {
	if src.Doc != nil {
		r.readDoc(src.Doc)
		src.Doc = nil  
	}
	for _, decl := range src.Decls {
		switch d := decl.(type) {
		case *ast.GenDecl:
			switch d.Tok {
			case token.IMPORT:
				for _, spec := range d.Specs {
					if s, ok := spec.(*ast.ImportSpec); ok {
						if import_, err := strconv.Unquote(s.Path.Value); err == nil {
							r.imports[import_] = 1
							if s.Name != nil && s.Name.Name == "." {
								r.hasDotImp = true
							}
						}
					}
				}
			case token.CONST, token.VAR:
				r.readValue(d)
			case token.TYPE:
				if len(d.Specs) == 1 && !d.Lparen.IsValid() {
					if s, ok := d.Specs[0].(*ast.TypeSpec); ok {
						r.readType(d, s)
					}
					break
				}
				for _, spec := range d.Specs {
					if s, ok := spec.(*ast.TypeSpec); ok {
						fake := &ast.GenDecl{
							Doc: d.Doc,
							TokPos: s.Pos(),
							Tok:    token.TYPE,
							Specs:  []ast.Spec{s},
						}
						r.readType(fake, s)
					}
				}
			}
		case *ast.FuncDecl:
			r.readFunc(d)
		}
	}
	r.readNotes(src.Comments)
	src.Comments = nil  
}
func (r *reader) readPackage(pkg *ast.Package, mode Mode) {
	r.filenames = make([]string, len(pkg.Files))
	r.imports = make(map[string]int)
	r.mode = mode
	r.types = make(map[string]*namedType)
	r.funcs = make(methodSet)
	r.notes = make(map[string][]*Note)
	i := 0
	for filename := range pkg.Files {
		r.filenames[i] = filename
		i++
	}
	sort.Strings(r.filenames)
	for _, filename := range r.filenames {
		f := pkg.Files[filename]
		if mode&AllDecls == 0 {
			r.fileExports(f)
		}
		r.readFile(f)
	}
}
func customizeRecv(f *Func, recvTypeName string, embeddedIsPtr bool, level int) *Func {
	if f == nil || f.Decl == nil || f.Decl.Recv == nil || len(f.Decl.Recv.List) != 1 {
		return f  
	}
	newField := *f.Decl.Recv.List[0]
	origPos := newField.Type.Pos()
	_, origRecvIsPtr := newField.Type.(*ast.StarExpr)
	newIdent := &ast.Ident{NamePos: origPos, Name: recvTypeName}
	var typ ast.Expr = newIdent
	if !embeddedIsPtr && origRecvIsPtr {
		newIdent.NamePos++  
		typ = &ast.StarExpr{Star: origPos, X: newIdent}
	}
	newField.Type = typ
	newFieldList := *f.Decl.Recv
	newFieldList.List = []*ast.Field{&newField}
	newFuncDecl := *f.Decl
	newFuncDecl.Recv = &newFieldList
	newF := *f
	newF.Decl = &newFuncDecl
	newF.Recv = recvString(typ)
	newF.Level = level
	return &newF
}
func (r *reader) collectEmbeddedMethods(mset methodSet, typ *namedType, recvTypeName string, embeddedIsPtr bool, level int, visited embeddedSet) {
	visited[typ] = true
	for embedded, isPtr := range typ.embedded {
		thisEmbeddedIsPtr := embeddedIsPtr || isPtr
		for _, m := range embedded.methods {
			if m.Level == 0 {
				mset.add(customizeRecv(m, recvTypeName, thisEmbeddedIsPtr, level))
			}
		}
		if !visited[embedded] {
			r.collectEmbeddedMethods(mset, embedded, recvTypeName, thisEmbeddedIsPtr, level+1, visited)
		}
	}
	delete(visited, typ)
}