Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(280)

Unified Diff: tool/gen.go

Issue 5504046: remotize kit initial code review
Patch Set: Created 12 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « tool/detect.go ('k') | tool/tool_test.go » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: tool/gen.go
diff --git a/tool/gen.go b/tool/gen.go
new file mode 100644
index 0000000000000000000000000000000000000000..d5b761a2a1e0c20dac8664b8a05e14f5314aab81
--- /dev/null
+++ b/tool/gen.go
@@ -0,0 +1,558 @@
+// Copyright 2011 Jose Luis Vázquez González josvazg@gmail.com
+// Use of this source code is governed by a BSD-style
+
+package tool
+
+import (
+ "bytes"
+ "exec"
+ "fmt"
+ "github.com/josvazg/remotize"
+ "io"
+ "os"
+ "reflect"
+ "sort"
+ "strconv"
+ "strings"
+)
+
+const remotizePkg = "github.com/josvazg/remotize"
+
+// remotizer code head and tail & marker
+const (
+ remotizerHead = `// Autogenerated Remotizer [DO NOT EDIT!]
+package main
+
+`
+ remotizerTail = `func main() {
+ for _,spec := range toremotize {
+ if e:=tool.Remotize(spec); e!=nil {
+ panic(e)
+ }
+ }
+}`
+)
+
+// The General Spec for a Remotization
+type Spec struct {
+ packname string
+ name string
+ isInterface bool
+ t reflect.Type
+ imports map[string]string
+}
+
+// NewSpec will create an Spec to be remotized.
+func NewSpec(pack string, isInterface bool, i interface{}) *Spec {
+ t := reflect.TypeOf(i)
+ bt := baseType(t)
+ return &Spec{pack, bt.Name(), isInterface, bt, make(map[string]string)}
+}
+
+// Value2Spec turns a sample value into a remotization Spec for that kind of value.
+func Value2Spec(pack string, i interface{}) *Spec {
+ return Type2Spec(pack, reflect.TypeOf(i))
+}
+
+// Type2Spec turns a type into a Spec to be remotized.
+func Type2Spec(pack string, t reflect.Type) *Spec {
+ bt := baseType(t)
+ isInterface := (bt.Kind() == reflect.Interface)
+ if t.NumMethod() == 0 {
+ t = bt
+ }
+ return &Spec{pack, ifacename(bt.Name()), isInterface, t, make(map[string]string)}
+}
+
+// Remotize remotizes a type, interface or source code specified in a Spec by generating
+// the correct wrapper for that type.
+func Remotize(spec *Spec) os.Error {
+ if spec.name == "" {
+ return os.NewError(fmt.Sprintf("Can't remotize unnamed interface from ", spec))
+ }
+ def := spec.buildInterfaceDef()
+ hdr := spec.buildHeader()
+ body := spec.buildBody()
+ if spec.isInterface {
+ def = ""
+ }
+ source := fmt.Sprintf("%s%s%s", hdr, def, body)
+ return gofmtSave("remotized"+spec.name, source)
+}
+
+// nameOf returns the base NON pointer type referred by t
+func baseType(t reflect.Type) reflect.Type {
+ if t.Kind() == reflect.Ptr {
+ return baseType(t.Elem())
+ }
+ return t
+}
+
+// buildInterfaceDef builds the interface definition and fills the imports field
+func (s *Spec) buildInterfaceDef() string {
+ def := bytes.NewBufferString("")
+ ifacename := s.name
+ if !s.isInterface {
+ ifacename += remotize.Suffix(s.name)
+ }
+ fmt.Fprintf(def, "type %s interface", ifacename)
+ if s.t.NumMethod() > 0 {
+ fmt.Fprintf(def, " {")
+ for i := 0; i < s.t.NumMethod(); i++ {
+ m := s.t.Method(i)
+ if isExported(m.Name) {
+ fmt.Fprintf(def, "\n ")
+ s.funcsource(def, s.t, &m)
+ }
+ }
+ fmt.Fprintf(def, "\n}\n\n")
+ }
+ return def.String()
+}
+
+// funcsource will generate the source code for a function declaration (no body)
+func (s *Spec) funcsource(w io.Writer, t reflect.Type, m *reflect.Method) {
+ start := 0
+ if t.Kind() == reflect.Interface {
+ fmt.Fprintf(w, m.Name+"(")
+ } else if m != nil && m.Name != "" {
+ start++
+ fmt.Fprintf(w, m.Name+"(")
+ } else {
+ fmt.Fprintf(w, "func (")
+ }
+ if m != nil {
+ t = m.Type
+ }
+ for i := start; i < t.NumIn(); i++ {
+ s.typesource(w, t.In(i))
+ if (i + 1) != t.NumIn() {
+ fmt.Fprintf(w, ", ")
+ }
+ }
+ fmt.Fprintf(w, ") ")
+ if t.NumOut() > 1 {
+ fmt.Fprintf(w, "(")
+ }
+ for i := 0; i < t.NumOut(); i++ {
+ s.typesource(w, t.Out(i))
+ if (i + 1) != t.NumOut() {
+ fmt.Fprintf(w, ", ")
+ }
+ }
+ if t.NumOut() > 1 {
+ fmt.Fprintf(w, ")")
+ }
+}
+
+// typesource will generate the source code for a type
+func (s *Spec) typesource(w io.Writer, t reflect.Type) {
+ switch t.Kind() {
+ case reflect.Array:
+ fmt.Fprintf(w, "["+strconv.Itoa(t.Len())+"]")
+ s.typesource(w, t.Elem())
+ case reflect.Chan:
+ fmt.Fprintf(w, "chan ")
+ s.typesource(w, t.Elem())
+ case reflect.Func:
+ s.funcsource(w, t, nil)
+ case reflect.Map:
+ fmt.Fprintf(w, "map[")
+ s.typesource(w, t.Key())
+ fmt.Fprintf(w, "]")
+ s.typesource(w, t.Elem())
+ case reflect.Ptr:
+ fmt.Fprintf(w, "*")
+ s.typesource(w, t.Elem())
+ case reflect.Slice:
+ fmt.Fprintf(w, "[]")
+ s.typesource(w, t.Elem())
+ case reflect.String:
+ fmt.Fprintf(w, "string")
+ default:
+ s.pack(t)
+ name := t.String()
+ if t.PkgPath() == s.packname {
+ name = t.Name()
+ }
+ fmt.Fprintf(w, name)
+ return
+ }
+}
+
+// pack will record the type's package as an import
+func (s *Spec) pack(t reflect.Type) {
+ packpath := t.PkgPath()
+ if packpath != "" {
+ alias := path2pack(packpath)
+ s.imports[alias] = packpath
+ }
+}
+
+// buildHeader will build the header for the source file including package and imports
+func (s *Spec) buildHeader() string {
+ hdr := bytes.NewBufferString("// Autogenerated by josvazg/remotize/tool - no need to edit!\n")
+ fmt.Fprintf(hdr, "package %v\n\n", path2pack(s.packname))
+ if s.imports == nil {
+ s.imports = make(map[string]string)
+ }
+ s.imports["rpc"] = "rpc"
+ s.imports["os"] = "os"
+ typepack := baseType(s.t).PkgPath()
+ if s.packname != typepack && typepack != "main" && s.t.Kind() == reflect.Interface {
+ s.imports[typepack] = typepack
+ }
+ if s.packname != remotizePkg {
+ s.imports[remotizePkg] = remotizePkg
+ }
+ names := make([]string, 0)
+ for _, name := range s.imports {
+ names = append(names, name)
+ }
+ writeImports(hdr, names, s.imports, s.packname)
+ return hdr.String()
+}
+
+// buildBody builds the wrapper body source code
+func (s *Spec) buildBody() string {
+ src := bytes.NewBufferString("")
+ fmt.Fprintf(src, "// Autoregistry\n")
+ fmt.Fprintf(src, "func init() {\n")
+ fmt.Fprintf(src, " remotize.Register(Remote%s{},\n", s.name)
+ fmt.Fprintf(src, " func(cli *rpc.Client) interface{} "+
+ "{\n\t\t\treturn NewRemote%s(cli)\n\t\t},\n", s.name)
+ fmt.Fprintf(src, " %sService{},\n", s.name)
+ fmt.Fprintf(src, " func(i interface{}) interface{} {")
+ fmt.Fprintf(src, "\n\t\t\treturn New%sService(i.(%s))\n\t\t},\n", s.name, s.fullname())
+ fmt.Fprintf(src, " )\n")
+ fmt.Fprintf(src, "}\n\n")
+ s.remoteInit(src)
+ s.localInit(src)
+ for i := 0; i < s.t.NumMethod(); i++ {
+ m := s.t.Method(i)
+ if isExported(m.Name) {
+ s.wrapMethod(src, m)
+ }
+ }
+ return src.String()
+}
+
+// fullname return the appropiate full name with or without package prefix for the remotized type
+func (s *Spec) fullname() string {
+ if !s.isInterface || s.t.PkgPath() == s.packname || s.t.PkgPath() == "main" {
+ return s.name
+ }
+ return s.t.String()
+}
+
+// remoteInit prepares the service header
+func (s *Spec) remoteInit(w io.Writer) {
+ fmt.Fprintf(w, "// Rpc service wrapper for %s\n", s.name)
+ fmt.Fprintf(w, "type %sService struct {\n", s.name)
+ fmt.Fprintf(w, " srv %s\n", s.fullname())
+ fmt.Fprintf(w, "}\n\n")
+ fmt.Fprintf(w, "// Direct %sService constructor\n", s.name)
+ fmt.Fprintf(w, "func New%sService(impl %s) *%sService {\n", s.name, s.fullname(), s.name)
+ fmt.Fprintf(w, " return &%sService{impl}\n", s.name)
+ fmt.Fprintf(w, "}\n\n")
+}
+
+// localInit prepares the client header
+func (s *Spec) localInit(w io.Writer) {
+ fmt.Fprintf(w, "// Rpc client for %s\n", s.name)
+ fmt.Fprintf(w, "type Remote%s struct {\n", s.name)
+ fmt.Fprintf(w, " cli *rpc.Client\n")
+ fmt.Fprintf(w, "}\n\n")
+ fmt.Fprintf(w, "// Direct Remote%s constructor\n", s.name)
+ fmt.Fprintf(w, "func NewRemote%s(cli *rpc.Client) *Remote%s {\n", s.name, s.name)
+ fmt.Fprintf(w, " return &Remote%s{cli}\n", s.name)
+ fmt.Fprintf(w, "}\n\n")
+}
+
+// wrapMethod generates the wrappers for one method
+func (s *Spec) wrapMethod(w io.Writer, m reflect.Method) {
+ fmt.Fprintf(w, "// wrapper for: %s\n\n", m.Name)
+ args := make([]reflect.Type, 0)
+ start := 0
+ if s.t.Kind() != reflect.Interface { // avoid the first receiver arg on non interfaces
+ start = 1
+ }
+ for i := start; i < m.Type.NumIn(); i++ {
+ args = append(args, m.Type.In(i))
+ }
+ s.generateStructWrapper(w, args, "Args", m.Name)
+ results, inouts := prepareInOuts(m.Type, start)
+ s.generateStructWrapper(w, results, "Reply", m.Name)
+ s.generateServerRPCWrapper(w, m, inouts, start)
+ s.generateClientRPCWrapper(w, m, inouts, start)
+ fmt.Fprintf(w, "\n")
+}
+
+// generateStructWrapper generates a argument or result struct
+func (s *Spec) generateStructWrapper(w io.Writer, pars []reflect.Type, structname, name string) {
+ fmt.Fprintf(w, "type %s%s%s struct {\n", s.name, name, structname)
+ for i, par := range pars {
+ fmt.Fprintf(w, "\tArg%d ", i)
+ s.typesource(w, par)
+ fmt.Fprintf(w, "\n")
+ }
+ fmt.Fprintf(w, "}\n\n")
+}
+
+// prepareInOuts detects how many pointers in the args must be returned as results (in & outs)
+func prepareInOuts(ft reflect.Type, start int) ([]reflect.Type, []int) {
+ results := make([]reflect.Type, 0)
+ inouts := make([]int, 0)
+ for i := start; i < ft.NumIn(); i++ {
+ if ft.In(i).Kind() == reflect.Ptr {
+ results = append(results, ft.In(i))
+ inouts = append(inouts, i)
+ }
+ }
+ for i := 0; i < ft.NumOut(); i++ {
+ results = append(results, ft.Out(i))
+ }
+ return results, inouts
+}
+
+// function that is exposed to an RPC API, but calls simple "Server_" one
+func (s *Spec) generateServerRPCWrapper(w io.Writer, m reflect.Method, inouts []int, start int) {
+ name := m.Name
+ ins := m.Type.NumIn()
+ outs := m.Type.NumOut()
+ fmt.Fprintf(w, "func (r *%sService) %s(args *%s%sArgs, "+
+ "reply *%s%sReply) os.Error {\n", s.name, name, s.name, name, s.name, name)
+ fmt.Fprintf(w, "\t")
+ for i := 0; i < outs; i++ {
+ fmt.Fprintf(w, "reply.Arg%d", i)
+ if i != outs-1 {
+ fmt.Fprintf(w, ", ")
+ }
+ }
+ if outs > 0 {
+ fmt.Fprintf(w, " = ")
+ }
+ fmt.Fprintf(w, "r.srv.%s(", name)
+ for i := start; i < ins; i++ {
+ fmt.Fprintf(w, "args.Arg%d", i-start)
+ if i != ins-1 {
+ fmt.Fprintf(w, ", ")
+ }
+ }
+ fmt.Fprintf(w, ")\n")
+ for i := outs; i < len(inouts); i++ {
+ fmt.Fprintf(w, "\treply.Arg%d=args.Arg%d\n", i, inouts[i-outs])
+ }
+ fmt.Fprintf(w, "\treturn nil\n}\n\n")
+}
+
+// generateClientRPCWrapper generates the client side wrapper
+func (s *Spec) generateClientRPCWrapper(w io.Writer, m reflect.Method, inouts []int, start int) {
+ name := m.Name
+ ins := m.Type.NumIn()
+ outs := m.Type.NumOut()
+ fmt.Fprintf(w, "func (l *Remote%s) %s(", s.name, name)
+ s.printFuncFieldListUsingArgs(w, m.Type, start)
+ fmt.Fprintf(w, ") ")
+ s.printFuncResultList(w, m.Type)
+ fmt.Fprintf(w, "{\n")
+ fmt.Fprintf(w, "\tvar args %s%sArgs\n", s.name, name)
+ fmt.Fprintf(w, "\tvar reply %s%sReply\n", s.name, name)
+ for i := start; i < ins; i++ {
+ fmt.Fprintf(w, "\targs.Arg%d = Arg%d\n", i-start, i-start)
+ }
+ fmt.Fprintf(w, "\terr := l.cli.Call(\"%sService.%s\", &args, &reply)\n", s.name, name)
+ fmt.Fprintf(w, "\tif err != nil {\n")
+ fmt.Fprintf(w, "\t\tpanic(err.String())\n\t}\n")
+ for i := outs; i < len(inouts); i++ {
+ fmt.Fprintf(w, "\t*Arg%d=*reply.Arg%d\n", i, inouts[i-outs])
+ }
+ fmt.Fprintf(w, "\treturn ")
+ for i := 0; i < outs; i++ {
+ fmt.Fprintf(w, "reply.Arg%d", i)
+ if i != outs-1 {
+ fmt.Fprintf(w, ", ")
+ }
+ }
+ fmt.Fprintf(w, "\n}\n\n")
+}
+
+// printFuncFieldListUsingArgs generates the func field list with argX names
+func (s *Spec) printFuncFieldListUsingArgs(w io.Writer, t reflect.Type, start int) {
+ if t.IsVariadic() {
+ panic(fmt.Sprintf("Variadic argument lists as in '%v' are not supported!", t))
+ }
+ for i := start; i < t.NumIn(); i++ {
+ // names
+ fmt.Fprintf(w, "Arg%d ", i-start)
+ s.typesource(w, t.In(i))
+ // ,
+ if i != t.NumIn()-1 {
+ fmt.Fprintf(w, ", ")
+ }
+ }
+}
+
+// printFuncResultList generates the func field list with type names
+func (s *Spec) printFuncResultList(w io.Writer, t reflect.Type) {
+ outs := t.NumOut()
+ if outs > 1 {
+ fmt.Fprintf(w, "(")
+ }
+ for i := 0; i < outs; i++ {
+ // names
+ s.typesource(w, t.Out(i))
+ // ,
+ if i != outs-1 {
+ fmt.Fprintf(w, ", ")
+ }
+ }
+ if outs > 1 {
+ fmt.Fprintf(w, ") ")
+ } else {
+ fmt.Fprintf(w, " ")
+ }
+}
+
+// generateRemotizerCode returns the remotizer source code for a given set of Detected remotizables
+func generateRemotizerCode(pspecs []PreSpec) string {
+ src := bytes.NewBuffer(make([]byte, 0))
+ fmt.Fprintf(src, remotizerHead)
+ genImports(src, pspecs)
+ genTypeDefs(src, pspecs)
+ fmt.Fprintf(src, "var toremotize = []*tool.Spec{")
+ for _, ps := range pspecs {
+ // complete types with methods...
+ if dcl,ok:=ps.(*decl); ok {
+ fmt.Fprintf(src, "\n\ttool.NewSpec(\"%v\",", ps.packname())
+ fmt.Fprintf(src, "%v,", dcl.isInterface)
+ fmt.Fprintf(src, "new(%v)),", ifacename(ps.name()))
+ }
+ // ... or just a type name predefined elsewhere
+ if _,ok:=ps.(*predefined); ok {
+ fmt.Fprintf(src, "\n\ttool.Value2Spec(\"%v\",new(%v)),", ps.packname(), ps.name())
+ }
+ }
+ fmt.Fprintf(src, "\n}\n\n")
+ fmt.Fprintf(src, remotizerTail)
+ return src.String()
+}
+
+// genImports adds imports to the remotizer source code from types
+func genImports(src io.Writer, pspecs []PreSpec) {
+ imports := []string{"tool"}
+ aliases:=make(map[string]string)
+ aliases["tool"] = "github.com/josvazg/remotize/tool"
+ for _, pspec:=range pspecs {
+ if decl,ok:=pspec.(*decl); ok {
+ for alias,name:=range decl.detected.aliases {
+ aliases[alias]=name
+ }
+ for packname, _ := range decl.imports {
+ imports = addImport(imports, packname)
+ }
+ } else if _,ok:=pspec.(*predefined); ok {
+ packname := strings.SplitN(pspec.name(), ".", 2)[0]
+ imports = addImport(imports, packname)
+ }
+ }
+ writeImports(src, imports, aliases, "")
+}
+
+// addImport will add a packname import into a list making sure it's nor repeated
+func addImport(imports []string, packname string) []string {
+ for _, imp := range imports {
+ if packname == imp {
+ return imports
+ }
+ }
+ return append(imports, packname)
+}
+
+// genTypeDefs adds types and interface source code definitions from this packages to the remotizer
+func genTypeDefs(src io.Writer, pspecs []PreSpec) {
+ for _, pspec := range pspecs {
+ if decl,ok:=pspec.(*decl); ok && decl.Src != nil {
+ fmt.Fprintf(src, "\n%s\n", decl.Src)
+ }
+ }
+}
+
+// writeImports will generate an import set source code
+func writeImports(w io.Writer, imports []string, aliases map[string]string, skip string) {
+ sort.Strings(imports)
+ fmt.Fprintf(w, "import (\n")
+ for _, s := range imports {
+ v := aliases[s]
+ if v == skip || v == "" {
+ continue
+ }
+ if s == v || strings.HasSuffix(v, "/"+s) {
+ fmt.Fprintf(w, "\t\"%s\"\n", v)
+ } else {
+ fmt.Fprintf(w, "\t%s \"%s\"\n", s, v)
+ }
+ }
+ fmt.Fprintf(w, ")\n\n")
+}
+
+// BuildRemotizer generates a program to remotize the detected interfaces.
+func BuildRemotizer(pspecs []PreSpec) os.Error {
+ src := generateRemotizerCode(pspecs)
+ filename := "_remotizer"
+ if e := gofmtSave(filename, src); e != nil {
+ return e
+ }
+ if o, e := runCmd(gocompile(), "-I", "_test", filename+".go"); e != nil {
+ fmt.Fprintf(os.Stderr, string(o)+"\n")
+ return e
+ }
+ if o, e := runCmd(golink(), "-L", "_test", "-o", filename,
+ filename+"."+goext()); e != nil {
+ fmt.Fprintf(os.Stderr, string(o)+"\n")
+ return e
+ }
+ if o, e := runCmd("./" + filename); e != nil {
+ fmt.Fprintf(os.Stderr, string(o)+"\n")
+ return e
+ }
+ return nil
+}
+
+// runs a command
+func runCmd(cmdargs ...string) ([]byte, os.Error) {
+ fmt.Println(cmdargs)
+ return exec.Command(cmdargs[0], cmdargs[1:]...).CombinedOutput()
+}
+
+// dictionary cache
+var dict map[string]string
+
+// go tool execution string
+func goexec(tool string) string {
+ if dict == nil {
+ dict = make(map[string]string)
+ dict["386"] = "8"
+ dict["amd64"] = "6"
+ dict["arm"] = "5"
+ dict["compiler"] = "g"
+ dict["linker"] = "l"
+ }
+ return dict[os.Getenv("GOARCH")] + dict[tool]
+}
+
+// Go compiler
+func gocompile() string {
+ return goexec("compiler")
+}
+
+// Go linker
+func golink() string {
+ return goexec("linker")
+}
+
+// Go architecture extension
+func goext() string {
+ return goexec("")
+}
+
« no previous file with comments | « tool/detect.go ('k') | tool/tool_test.go » ('j') | no next file with comments »

Powered by Google App Engine
RSS Feeds Recent Issues | This issue
This is Rietveld f62528b