github.com/neugram/ng@v0.0.0-20180309130942-d472ff93d872/eval/gowrap/genwrap/genwrap.go (about)

     1  // Copyright 2017 The Neugram Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package genwrap
     6  
     7  import (
     8  	"bytes"
     9  	"fmt"
    10  	"go/ast"
    11  	"go/format"
    12  	"go/types"
    13  	"log"
    14  	"strings"
    15  	"text/template"
    16  
    17  	"neugram.io/ng/gotool"
    18  )
    19  
    20  func quotePkgPath(path string) string {
    21  	return "wrap_" + strings.NewReplacer(
    22  		"/", "_",
    23  		".", "_",
    24  		"-", "_",
    25  	).Replace(path)
    26  }
    27  
    28  func buildDataPkg(pkgPath string, pkg *types.Package) DataPkg {
    29  	quotedPkgPath := quotePkgPath(pkgPath)
    30  	scope := pkg.Scope()
    31  	exports := map[string]string{}
    32  	for _, name := range scope.Names() {
    33  		if !ast.IsExported(name) {
    34  			continue
    35  		}
    36  		obj := scope.Lookup(name)
    37  		switch obj.(type) {
    38  		case *types.TypeName:
    39  			if _, ok := obj.Type().Underlying().(*types.Interface); ok {
    40  				exports[name] = "reflect.ValueOf(reflect.TypeOf((*" + quotedPkgPath + "." + name + ")(nil)).Elem())"
    41  			} else {
    42  				exports[name] = "reflect.ValueOf(reflect.TypeOf(" + quotedPkgPath + "." + name + nilexpr(obj.Type()) + "))"
    43  			}
    44  		case *types.Var:
    45  			exports[name] = "reflect.ValueOf(&" + quotedPkgPath + "." + name + ").Elem()"
    46  		case *types.Func, *types.Const:
    47  			exports[name] = "reflect.ValueOf(" + quotedPkgPath + "." + name + ")"
    48  		default:
    49  			log.Printf("genwrap: unexpected obj: %T\n", obj)
    50  		}
    51  	}
    52  
    53  	return DataPkg{
    54  		Name:       pkg.Path(),
    55  		QuotedName: quotedPkgPath,
    56  		Exports:    exports,
    57  	}
    58  }
    59  
    60  // GenGo generates a wrapper package naemd outPkgName that
    61  // registers the exported symbols of pkgPath with the global
    62  // map gopkg.Pkgs.
    63  //
    64  // Any other packages that pkgPath depends on for defining its
    65  // exported symbols are also registered, unless skipDeps is set.
    66  func GenGo(pkgPath, outPkgName string, skipDeps bool) ([]byte, error) {
    67  	pkg, err := gotool.M.ImportGo(pkgPath)
    68  	if err != nil {
    69  		return nil, err
    70  	}
    71  	pkgs := make(map[string]DataPkg)
    72  	pkgs[pkgPath] = buildDataPkg(pkgPath, pkg)
    73  	imports := []*types.Package{}
    74  	if !skipDeps {
    75  		for _, imp := range pkg.Imports() {
    76  			// Re-import package to get all exported symbols.
    77  			imppkg, err := gotool.M.ImportGo(imp.Path())
    78  			if err != nil {
    79  				return nil, err
    80  			}
    81  			imports = append(imports, imppkg)
    82  		}
    83  	}
    84  
    85  importsLoop:
    86  	for i := 0; i < len(imports); i++ { // imports grows as we loop
    87  		path := imports[i].Path()
    88  		if _, exists := pkgs[path]; exists {
    89  			continue
    90  		}
    91  		for _, dir := range strings.Split(path, "/") {
    92  			if dir == "internal" || dir == "vendor" {
    93  				continue importsLoop
    94  			}
    95  		}
    96  		pkgs[path] = buildDataPkg(path, imports[i])
    97  	}
    98  	data := Data{
    99  		OutPkgName: outPkgName,
   100  	}
   101  	for _, dataPkg := range pkgs {
   102  		data.Pkgs = append(data.Pkgs, dataPkg)
   103  	}
   104  
   105  	buf := new(bytes.Buffer)
   106  	err = tmpl.Execute(buf, data)
   107  	if err != nil {
   108  		return nil, fmt.Errorf("genwrap: %v", err)
   109  	}
   110  	res, err := format.Source(buf.Bytes())
   111  	if err != nil {
   112  		lines := new(bytes.Buffer)
   113  		for i, line := range strings.Split(buf.String(), "\n") {
   114  			fmt.Fprintf(lines, "%3d: %s\n", i, line)
   115  		}
   116  		return nil, fmt.Errorf("genwrap: bad generated source: %v\n%s", err, lines.String())
   117  	}
   118  	return res, nil
   119  }
   120  
   121  func nilexpr(t types.Type) string {
   122  	t = t.Underlying()
   123  	switch t := t.(type) {
   124  	case *types.Basic:
   125  		switch t.Kind() {
   126  		case types.Bool:
   127  			return "(false)"
   128  		case types.String:
   129  			return `("")`
   130  		case types.UnsafePointer:
   131  			return "(nil)"
   132  		default:
   133  			return "(0)"
   134  		}
   135  	case *types.Array, *types.Struct:
   136  		return "{}"
   137  	case *types.Interface, *types.Map, *types.Pointer, *types.Slice, *types.Signature:
   138  		return "(nil)"
   139  	default:
   140  		return fmt.Sprintf("(unexpected type: %T)", t)
   141  	}
   142  }
   143  
   144  type Data struct {
   145  	OutPkgName string
   146  	Pkgs       []DataPkg
   147  }
   148  
   149  type DataPkg struct {
   150  	Name       string
   151  	QuotedName string
   152  	Exports    map[string]string
   153  }
   154  
   155  var tmpl = template.Must(template.New("genwrap").Parse(`
   156  // Generated file, do not edit.
   157  
   158  package {{.OutPkgName}}
   159  
   160  import (
   161  	"reflect"
   162  
   163  	"neugram.io/ng/eval/gowrap"
   164  
   165  {{range .Pkgs}}
   166  	{{.QuotedName}} "{{.Name}}"
   167  {{end}}
   168  )
   169  
   170  {{range .Pkgs}}
   171  var pkg_{{.QuotedName}} = &gowrap.Pkg{
   172  	Exports: map[string]reflect.Value{
   173  		{{with $data := .}}
   174  		{{range $name, $export := $data.Exports}}
   175  		"{{$name}}": {{$export}},{{end}}
   176  		{{end}}
   177  	},
   178  }
   179  {{end}}
   180  
   181  {{range .Pkgs}}
   182  func init() {
   183  	if gowrap.Pkgs["{{.Name}}"] == nil {
   184  		gowrap.Pkgs["{{.Name}}"] = pkg_{{.QuotedName}}
   185  	}
   186  }
   187  {{end}}
   188  `))