|
|
package parse
import ( "fmt" "go/ast" "go/parser" "go/token" "os" "reflect" "sort" "strings"
"github.com/tinylib/msgp/gen" "github.com/ttacon/chalk" )
// A FileSet is the in-memory representation of a
// parsed file.
type FileSet struct { Package string // package name
Specs map[string]ast.Expr // type specs in file
Identities map[string]gen.Elem // processed from specs
Directives []string // raw preprocessor directives
Imports []*ast.ImportSpec // imports
}
// File parses a file at the relative path
// provided and produces a new *FileSet.
// If you pass in a path to a directory, the entire
// directory will be parsed.
// If unexport is false, only exported identifiers are included in the FileSet.
// If the resulting FileSet would be empty, an error is returned.
func File(name string, unexported bool) (*FileSet, error) { pushstate(name) defer popstate() fs := &FileSet{ Specs: make(map[string]ast.Expr), Identities: make(map[string]gen.Elem), }
fset := token.NewFileSet() finfo, err := os.Stat(name) if err != nil { return nil, err } if finfo.IsDir() { pkgs, err := parser.ParseDir(fset, name, nil, parser.ParseComments) if err != nil { return nil, err } if len(pkgs) != 1 { return nil, fmt.Errorf("multiple packages in directory: %s", name) } var one *ast.Package for _, nm := range pkgs { one = nm break } fs.Package = one.Name for _, fl := range one.Files { pushstate(fl.Name.Name) fs.Directives = append(fs.Directives, yieldComments(fl.Comments)...) if !unexported { ast.FileExports(fl) } fs.getTypeSpecs(fl) popstate() } } else { f, err := parser.ParseFile(fset, name, nil, parser.ParseComments) if err != nil { return nil, err } fs.Package = f.Name.Name fs.Directives = yieldComments(f.Comments) if !unexported { ast.FileExports(f) } fs.getTypeSpecs(f) }
if len(fs.Specs) == 0 { return nil, fmt.Errorf("no definitions in %s", name) }
fs.process() fs.applyDirectives() fs.propInline()
return fs, nil }
// applyDirectives applies all of the directives that
// are known to the parser. additional method-specific
// directives remain in f.Directives
func (f *FileSet) applyDirectives() { newdirs := make([]string, 0, len(f.Directives)) for _, d := range f.Directives { chunks := strings.Split(d, " ") if len(chunks) > 0 { if fn, ok := directives[chunks[0]]; ok { pushstate(chunks[0]) err := fn(chunks, f) if err != nil { warnln(err.Error()) } popstate() } else { newdirs = append(newdirs, d) } } } f.Directives = newdirs }
// A linkset is a graph of unresolved
// identities.
//
// Since gen.Ident can only represent
// one level of type indirection (e.g. Foo -> uint8),
// type declarations like `type Foo Bar`
// aren't resolve-able until we've processed
// everything else.
//
// The goal of this dependency resolution
// is to distill the type declaration
// into just one level of indirection.
// In other words, if we have:
//
// type A uint64
// type B A
// type C B
// type D C
//
// ... then we want to end up
// figuring out that D is just a uint64.
type linkset map[string]*gen.BaseElem
func (f *FileSet) resolve(ls linkset) { progress := true for progress && len(ls) > 0 { progress = false for name, elem := range ls { real, ok := f.Identities[elem.TypeName()] if ok { // copy the old type descriptor,
// alias it to the new value,
// and insert it into the resolved
// identities list
progress = true nt := real.Copy() nt.Alias(name) f.Identities[name] = nt delete(ls, name) } } }
// what's left can't be resolved
for name, elem := range ls { warnf("couldn't resolve type %s (%s)\n", name, elem.TypeName()) } }
// process takes the contents of f.Specs and
// uses them to populate f.Identities
func (f *FileSet) process() {
deferred := make(linkset) parse: for name, def := range f.Specs { pushstate(name) el := f.parseExpr(def) if el == nil { warnln("failed to parse") popstate() continue parse } // push unresolved identities into
// the graph of links and resolve after
// we've handled every possible named type.
if be, ok := el.(*gen.BaseElem); ok && be.Value == gen.IDENT { deferred[name] = be popstate() continue parse } el.Alias(name) f.Identities[name] = el popstate() }
if len(deferred) > 0 { f.resolve(deferred) } }
func strToMethod(s string) gen.Method { switch s { case "encode": return gen.Encode case "decode": return gen.Decode case "test": return gen.Test case "size": return gen.Size case "marshal": return gen.Marshal case "unmarshal": return gen.Unmarshal default: return 0 } }
func (f *FileSet) applyDirs(p *gen.Printer) { // apply directives of the form
//
// //msgp:encode ignore {{TypeName}}
//
loop: for _, d := range f.Directives { chunks := strings.Split(d, " ") if len(chunks) > 1 { for i := range chunks { chunks[i] = strings.TrimSpace(chunks[i]) } m := strToMethod(chunks[0]) if m == 0 { warnf("unknown pass name: %q\n", chunks[0]) continue loop } if fn, ok := passDirectives[chunks[1]]; ok { pushstate(chunks[1]) err := fn(m, chunks[2:], p) if err != nil { warnf("error applying directive: %s\n", err) } popstate() } else { warnf("unrecognized directive %q\n", chunks[1]) } } else { warnf("empty directive: %q\n", d) } } }
func (f *FileSet) PrintTo(p *gen.Printer) error { f.applyDirs(p) names := make([]string, 0, len(f.Identities)) for name := range f.Identities { names = append(names, name) } sort.Strings(names) for _, name := range names { el := f.Identities[name] el.SetVarname("z") pushstate(el.TypeName()) err := p.Print(el) popstate() if err != nil { return err } } return nil }
// getTypeSpecs extracts all of the *ast.TypeSpecs in the file
// into fs.Identities, but does not set the actual element
func (fs *FileSet) getTypeSpecs(f *ast.File) {
// collect all imports...
fs.Imports = append(fs.Imports, f.Imports...)
// check all declarations...
for i := range f.Decls {
// for GenDecls...
if g, ok := f.Decls[i].(*ast.GenDecl); ok {
// and check the specs...
for _, s := range g.Specs {
// for ast.TypeSpecs....
if ts, ok := s.(*ast.TypeSpec); ok { switch ts.Type.(type) {
// this is the list of parse-able
// type specs
case *ast.StructType, *ast.ArrayType, *ast.StarExpr, *ast.MapType, *ast.Ident: fs.Specs[ts.Name.Name] = ts.Type
} } } } } }
func fieldName(f *ast.Field) string { switch len(f.Names) { case 0: return stringify(f.Type) case 1: return f.Names[0].Name default: return f.Names[0].Name + " (and others)" } }
func (fs *FileSet) parseFieldList(fl *ast.FieldList) []gen.StructField { if fl == nil || fl.NumFields() == 0 { return nil } out := make([]gen.StructField, 0, fl.NumFields()) for _, field := range fl.List { pushstate(fieldName(field)) fds := fs.getField(field) if len(fds) > 0 { out = append(out, fds...) } else { warnln("ignored.") } popstate() } return out }
// translate *ast.Field into []gen.StructField
func (fs *FileSet) getField(f *ast.Field) []gen.StructField { sf := make([]gen.StructField, 1) var extension bool // parse tag; otherwise field name is field tag
if f.Tag != nil { body := reflect.StructTag(strings.Trim(f.Tag.Value, "`")).Get("msg") tags := strings.Split(body, ",") if len(tags) == 2 && tags[1] == "extension" { extension = true } // ignore "-" fields
if tags[0] == "-" { return nil } sf[0].FieldTag = tags[0] sf[0].RawTag = f.Tag.Value }
ex := fs.parseExpr(f.Type) if ex == nil { return nil }
// parse field name
switch len(f.Names) { case 0: sf[0].FieldName = embedded(f.Type) case 1: sf[0].FieldName = f.Names[0].Name default: // this is for a multiple in-line declaration,
// e.g. type A struct { One, Two int }
sf = sf[0:0] for _, nm := range f.Names { sf = append(sf, gen.StructField{ FieldTag: nm.Name, FieldName: nm.Name, FieldElem: ex.Copy(), }) } return sf } sf[0].FieldElem = ex if sf[0].FieldTag == "" { sf[0].FieldTag = sf[0].FieldName }
// validate extension
if extension { switch ex := ex.(type) { case *gen.Ptr: if b, ok := ex.Value.(*gen.BaseElem); ok { b.Value = gen.Ext } else { warnln("couldn't cast to extension.") return nil } case *gen.BaseElem: ex.Value = gen.Ext default: warnln("couldn't cast to extension.") return nil } } return sf }
// extract embedded field name
//
// so, for a struct like
//
// type A struct {
// io.Writer
// }
//
// we want "Writer"
func embedded(f ast.Expr) string { switch f := f.(type) { case *ast.Ident: return f.Name case *ast.StarExpr: return embedded(f.X) case *ast.SelectorExpr: return f.Sel.Name default: // other possibilities are disallowed
return "" } }
// stringify a field type name
func stringify(e ast.Expr) string { switch e := e.(type) { case *ast.Ident: return e.Name case *ast.StarExpr: return "*" + stringify(e.X) case *ast.SelectorExpr: return stringify(e.X) + "." + e.Sel.Name case *ast.ArrayType: if e.Len == nil { return "[]" + stringify(e.Elt) } return fmt.Sprintf("[%s]%s", stringify(e.Len), stringify(e.Elt)) case *ast.InterfaceType: if e.Methods == nil || e.Methods.NumFields() == 0 { return "interface{}" } } return "<BAD>" }
// recursively translate ast.Expr to gen.Elem; nil means type not supported
// expected input types:
// - *ast.MapType (map[T]J)
// - *ast.Ident (name)
// - *ast.ArrayType ([(sz)]T)
// - *ast.StarExpr (*T)
// - *ast.StructType (struct {})
// - *ast.SelectorExpr (a.B)
// - *ast.InterfaceType (interface {})
func (fs *FileSet) parseExpr(e ast.Expr) gen.Elem { switch e := e.(type) {
case *ast.MapType: if k, ok := e.Key.(*ast.Ident); ok && k.Name == "string" { if in := fs.parseExpr(e.Value); in != nil { return &gen.Map{Value: in} } } return nil
case *ast.Ident: b := gen.Ident(e.Name)
// work to resove this expression
// can be done later, once we've resolved
// everything else.
if b.Value == gen.IDENT { if _, ok := fs.Specs[e.Name]; !ok { warnf("non-local identifier: %s\n", e.Name) } } return b
case *ast.ArrayType:
// special case for []byte
if e.Len == nil { if i, ok := e.Elt.(*ast.Ident); ok && i.Name == "byte" { return &gen.BaseElem{Value: gen.Bytes} } }
// return early if we don't know
// what the slice element type is
els := fs.parseExpr(e.Elt) if els == nil { return nil }
// array and not a slice
if e.Len != nil { switch s := e.Len.(type) { case *ast.BasicLit: return &gen.Array{ Size: s.Value, Els: els, }
case *ast.Ident: return &gen.Array{ Size: s.String(), Els: els, }
case *ast.SelectorExpr: return &gen.Array{ Size: stringify(s), Els: els, }
default: return nil } } return &gen.Slice{Els: els}
case *ast.StarExpr: if v := fs.parseExpr(e.X); v != nil { return &gen.Ptr{Value: v} } return nil
case *ast.StructType: return &gen.Struct{Fields: fs.parseFieldList(e.Fields)}
case *ast.SelectorExpr: return gen.Ident(stringify(e))
case *ast.InterfaceType: // support `interface{}`
if len(e.Methods.List) == 0 { return &gen.BaseElem{Value: gen.Intf} } return nil
default: // other types not supported
return nil } }
func infof(s string, v ...interface{}) { pushstate(s) fmt.Printf(chalk.Green.Color(strings.Join(logctx, ": ")), v...) popstate() }
func infoln(s string) { pushstate(s) fmt.Println(chalk.Green.Color(strings.Join(logctx, ": "))) popstate() }
func warnf(s string, v ...interface{}) { pushstate(s) fmt.Printf(chalk.Yellow.Color(strings.Join(logctx, ": ")), v...) popstate() }
func warnln(s string) { pushstate(s) fmt.Println(chalk.Yellow.Color(strings.Join(logctx, ": "))) popstate() }
func fatalf(s string, v ...interface{}) { pushstate(s) fmt.Printf(chalk.Red.Color(strings.Join(logctx, ": ")), v...) popstate() }
var logctx []string
// push logging state
func pushstate(s string) { logctx = append(logctx, s) }
// pop logging state
func popstate() { logctx = logctx[:len(logctx)-1] }
|