github.com/neilgarb/delve@v1.9.2-nobreaks/_scripts/gen-starlark-bindings.go (about) 1 package main 2 3 import ( 4 "bytes" 5 "fmt" 6 "go/format" 7 "go/token" 8 "go/types" 9 "io/ioutil" 10 "log" 11 "os" 12 "strings" 13 "unicode" 14 15 "golang.org/x/tools/go/packages" 16 ) 17 18 // getSuitableMethods returns the list of methods of service/rpc2.RPCServer that are exported as API calls 19 func getSuitableMethods(pkg *types.Package, typename string) []*types.Func { 20 r := []*types.Func{} 21 mset := types.NewMethodSet(types.NewPointer(pkg.Scope().Lookup(typename).Type())) 22 for i := 0; i < mset.Len(); i++ { 23 fn := mset.At(i).Obj().(*types.Func) 24 25 if !fn.Exported() { 26 continue 27 } 28 29 if fn.Name() == "Command" || fn.Name() == "Restart" || fn.Name() == "State" { 30 r = append(r, fn) 31 continue 32 } 33 34 sig, ok := fn.Type().(*types.Signature) 35 if !ok { 36 continue 37 } 38 39 // arguments must be (args, *reply) 40 if sig.Params().Len() != 2 { 41 continue 42 } 43 if ntyp, isname := sig.Params().At(0).Type().(*types.Named); !isname { 44 continue 45 } else if _, isstr := ntyp.Underlying().(*types.Struct); !isstr { 46 continue 47 } 48 if _, isptr := sig.Params().At(1).Type().(*types.Pointer); !isptr { 49 continue 50 } 51 52 // return values must be (error) 53 if sig.Results().Len() != 1 { 54 continue 55 } 56 if sig.Results().At(0).Type().String() != "error" { 57 continue 58 } 59 60 r = append(r, fn) 61 } 62 return r 63 } 64 65 func fieldsOfStruct(typ types.Type) (fieldNames, fieldTypes []string) { 66 styp := typ.(*types.Named).Underlying().(*types.Struct) 67 for i := 0; i < styp.NumFields(); i++ { 68 fieldNames = append(fieldNames, styp.Field(i).Name()) 69 fieldTypes = append(fieldTypes, styp.Field(i).Type().String()) 70 } 71 return fieldNames, fieldTypes 72 } 73 74 func camelToDash(in string) string { 75 out := []rune{} 76 for i, ch := range in { 77 isupper := func(i int) bool { 78 ch := in[i] 79 return ch >= 'A' && ch <= 'Z' 80 } 81 82 if i > 0 && isupper(i) { 83 if !isupper(i - 1) { 84 out = append(out, '_') 85 } else if i+1 < len(in) && !isupper(i+1) { 86 out = append(out, '_') 87 } 88 } 89 out = append(out, unicode.ToLower(ch)) 90 } 91 return string(out) 92 } 93 94 type binding struct { 95 name string 96 fn *types.Func 97 98 argType, retType string 99 100 argNames []string 101 argTypes []string 102 } 103 104 func processServerMethods(serverMethods []*types.Func) []binding { 105 bindings := make([]binding, len(serverMethods)) 106 for i, fn := range serverMethods { 107 sig, _ := fn.Type().(*types.Signature) 108 argNames, argTypes := fieldsOfStruct(sig.Params().At(0).Type()) 109 110 name := camelToDash(fn.Name()) 111 112 switch name { 113 case "set": 114 // avoid collision with builtin that already exists in starlark 115 name = "set_expr" 116 case "command": 117 name = "raw_command" 118 default: 119 // remove list_ prefix, it looks better 120 const listPrefix = "list_" 121 if strings.HasPrefix(name, listPrefix) { 122 name = name[len(listPrefix):] 123 } 124 } 125 126 retType := sig.Params().At(1).Type().String() 127 switch fn.Name() { 128 case "Command": 129 retType = "rpc2.CommandOut" 130 case "Restart": 131 retType = "rpc2.RestartOut" 132 case "State": 133 retType = "rpc2.StateOut" 134 } 135 136 bindings[i] = binding{ 137 name: name, 138 fn: fn, 139 argType: sig.Params().At(0).Type().String(), 140 retType: retType, 141 argNames: argNames, 142 argTypes: argTypes, 143 } 144 } 145 return bindings 146 } 147 148 func removePackagePath(typePath string) string { 149 lastSlash := strings.LastIndex(typePath, "/") 150 if lastSlash < 0 { 151 return typePath 152 } 153 return typePath[lastSlash+1:] 154 } 155 156 func genMapping(bindings []binding) []byte { 157 buf := bytes.NewBuffer([]byte{}) 158 159 fmt.Fprintf(buf, "// DO NOT EDIT: auto-generated using _scripts/gen-starlark-bindings.go\n\n") 160 fmt.Fprintf(buf, "package starbind\n\n") 161 fmt.Fprintf(buf, "import ( \"go.starlark.net/starlark\" \n \"github.com/go-delve/delve/service/api\" \n \"github.com/go-delve/delve/service/rpc2\" \n \"fmt\" )\n\n") 162 fmt.Fprintf(buf, "func (env *Env) starlarkPredeclare() starlark.StringDict {\n") 163 fmt.Fprintf(buf, "r := starlark.StringDict{}\n\n") 164 165 for _, binding := range bindings { 166 fmt.Fprintf(buf, "r[%q] = starlark.NewBuiltin(%q, func(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {", binding.name, binding.name) 167 fmt.Fprintf(buf, "if err := isCancelled(thread); err != nil { return starlark.None, decorateError(thread, err) }\n") 168 fmt.Fprintf(buf, "var rpcArgs %s\n", removePackagePath(binding.argType)) 169 fmt.Fprintf(buf, "var rpcRet %s\n", removePackagePath(binding.retType)) 170 171 // unmarshal normal unnamed arguments 172 for i := range binding.argNames { 173 fmt.Fprintf(buf, "if len(args) > %d && args[%d] != starlark.None { err := unmarshalStarlarkValue(args[%d], &rpcArgs.%s, %q); if err != nil { return starlark.None, decorateError(thread, err) } }", i, i, i, binding.argNames[i], binding.argNames[i]) 174 175 switch binding.argTypes[i] { 176 case "*github.com/go-delve/delve/service/api.LoadConfig": 177 if binding.fn.Name() != "Stacktrace" { 178 fmt.Fprintf(buf, "else { cfg := env.ctx.LoadConfig(); rpcArgs.%s = &cfg }", binding.argNames[i]) 179 } 180 case "github.com/go-delve/delve/service/api.LoadConfig": 181 fmt.Fprintf(buf, "else { rpcArgs.%s = env.ctx.LoadConfig() }", binding.argNames[i]) 182 case "*github.com/go-delve/delve/service/api.EvalScope": 183 fmt.Fprintf(buf, "else { scope := env.ctx.Scope(); rpcArgs.%s = &scope }", binding.argNames[i]) 184 case "github.com/go-delve/delve/service/api.EvalScope": 185 fmt.Fprintf(buf, "else { rpcArgs.%s = env.ctx.Scope() }", binding.argNames[i]) 186 } 187 188 fmt.Fprintf(buf, "\n") 189 190 } 191 192 // unmarshal keyword arguments 193 if len(binding.argNames) > 0 { 194 fmt.Fprintf(buf, "for _, kv := range kwargs {\n") 195 fmt.Fprintf(buf, "var err error\n") 196 fmt.Fprintf(buf, "switch kv[0].(starlark.String) {\n") 197 for i := range binding.argNames { 198 fmt.Fprintf(buf, "case %q: ", binding.argNames[i]) 199 fmt.Fprintf(buf, "err = unmarshalStarlarkValue(kv[1], &rpcArgs.%s, %q)\n", binding.argNames[i], binding.argNames[i]) 200 } 201 fmt.Fprintf(buf, "default: err = fmt.Errorf(\"unknown argument %%q\", kv[0])") 202 fmt.Fprintf(buf, "}\n") 203 fmt.Fprintf(buf, "if err != nil { return starlark.None, decorateError(thread, err) }\n") 204 fmt.Fprintf(buf, "}\n") 205 } 206 207 fmt.Fprintf(buf, "err := env.ctx.Client().CallAPI(%q, &rpcArgs, &rpcRet)\n", binding.fn.Name()) 208 fmt.Fprintf(buf, "if err != nil { return starlark.None, err }\n") 209 fmt.Fprintf(buf, "return env.interfaceToStarlarkValue(rpcRet), nil\n") 210 211 fmt.Fprintf(buf, "})\n") 212 } 213 214 fmt.Fprintf(buf, "return r\n") 215 fmt.Fprintf(buf, "}\n") 216 217 return buf.Bytes() 218 } 219 220 func genDocs(bindings []binding) []byte { 221 var buf bytes.Buffer 222 223 fmt.Fprintf(&buf, "Function | API Call\n") 224 fmt.Fprintf(&buf, "---------|---------\n") 225 226 for _, binding := range bindings { 227 argNames := strings.Join(binding.argNames, ", ") 228 fmt.Fprintf(&buf, "%s(%s) | Equivalent to API call [%s](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.%s)\n", binding.name, argNames, binding.fn.Name(), binding.fn.Name()) 229 } 230 231 fmt.Fprintf(&buf, "dlv_command(command) | Executes the specified command as if typed at the dlv_prompt\n") 232 fmt.Fprintf(&buf, "read_file(path) | Reads the file as a string\n") 233 fmt.Fprintf(&buf, "write_file(path, contents) | Writes string to a file\n") 234 fmt.Fprintf(&buf, "cur_scope() | Returns the current evaluation scope\n") 235 fmt.Fprintf(&buf, "default_load_config() | Returns the current default load configuration\n") 236 237 return buf.Bytes() 238 } 239 240 const ( 241 startOfMappingTable = "<!-- BEGIN MAPPING TABLE -->" 242 endOfMappingTable = "<!-- END MAPPING TABLE -->" 243 ) 244 245 func spliceDocs(docpath string, docs []byte, outpath string) { 246 docbuf, err := ioutil.ReadFile(docpath) 247 if err != nil { 248 log.Fatalf("could not read doc file: %v", err) 249 } 250 251 v := strings.Split(string(docbuf), startOfMappingTable) 252 if len(v) != 2 { 253 log.Fatal("could not find start of mapping table") 254 } 255 header := v[0] 256 v = strings.Split(v[1], endOfMappingTable) 257 if len(v) != 2 { 258 log.Fatal("could not find end of mapping table") 259 } 260 footer := v[1] 261 262 outbuf := make([]byte, 0, len(header)+len(docs)+len(footer)+len(startOfMappingTable)+len(endOfMappingTable)+1) 263 outbuf = append(outbuf, []byte(header)...) 264 outbuf = append(outbuf, []byte(startOfMappingTable)...) 265 outbuf = append(outbuf, '\n') 266 outbuf = append(outbuf, docs...) 267 outbuf = append(outbuf, []byte(endOfMappingTable)...) 268 outbuf = append(outbuf, []byte(footer)...) 269 270 if outpath != "-" { 271 err = ioutil.WriteFile(outpath, outbuf, 0664) 272 if err != nil { 273 log.Fatalf("could not write documentation file: %v", err) 274 } 275 } else { 276 os.Stdout.Write(outbuf) 277 } 278 } 279 280 func usage() { 281 fmt.Fprintf(os.Stderr, "gen-starlark-bindings [doc|doc/dummy|go] <destination file>\n\n") 282 fmt.Fprintf(os.Stderr, "Writes starlark documentation (doc) or mapping file (go) to <destination file>. Specify doc/dummy to generated documentation without overwriting the destination file.\n") 283 os.Exit(1) 284 } 285 286 func main() { 287 if len(os.Args) != 3 { 288 usage() 289 } 290 kind := os.Args[1] 291 path := os.Args[2] 292 293 fset := &token.FileSet{} 294 cfg := &packages.Config{ 295 Mode: packages.LoadSyntax, 296 Fset: fset, 297 } 298 pkgs, err := packages.Load(cfg, "github.com/go-delve/delve/service/rpc2") 299 if err != nil { 300 log.Fatalf("could not load packages: %v", err) 301 } 302 303 var serverMethods []*types.Func 304 packages.Visit(pkgs, func(pkg *packages.Package) bool { 305 if pkg.PkgPath == "github.com/go-delve/delve/service/rpc2" { 306 serverMethods = getSuitableMethods(pkg.Types, "RPCServer") 307 } 308 return true 309 }, nil) 310 311 bindings := processServerMethods(serverMethods) 312 313 switch kind { 314 case "go": 315 mapping := genMapping(bindings) 316 317 outfh := os.Stdout 318 if path != "-" { 319 outfh, err = os.Create(path) 320 if err != nil { 321 log.Fatalf("could not create output file: %v", err) 322 } 323 defer outfh.Close() 324 } 325 326 src, err := format.Source(mapping) 327 if err != nil { 328 fmt.Fprintf(os.Stderr, "%s", string(mapping)) 329 log.Fatal(err) 330 } 331 outfh.Write(src) 332 333 case "doc": 334 docs := genDocs(bindings) 335 spliceDocs(path, docs, path) 336 337 case "doc/dummy": 338 docs := genDocs(bindings) 339 spliceDocs(path, docs, "-") 340 341 default: 342 usage() 343 } 344 }