Skip to content

Commit

Permalink
terminal/starbind: add online help for starlark (#3388)
Browse files Browse the repository at this point in the history
Adds a new starlark builtin 'help' that prints the list of available
builtins when called without arguments and help for the specified
builtin when passed an argument.

The help is autogenerated from godoc comments so it isn't always
exactly accurate for starlark (in particular we sometimes refer to the
In structs), but it's better than nothing.
  • Loading branch information
aarzilli committed Jun 12, 2023
1 parent e549a02 commit 7d8f476
Show file tree
Hide file tree
Showing 3 changed files with 160 additions and 8 deletions.
55 changes: 50 additions & 5 deletions _scripts/gen-starlark-bindings.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
"bytes"
"fmt"
"go/ast"
"go/format"
"go/token"
"go/types"
Expand Down Expand Up @@ -99,9 +100,11 @@ type binding struct {

argNames []string
argTypes []string

docStr string
}

func processServerMethods(serverMethods []*types.Func) []binding {
func processServerMethods(serverMethods []*types.Func, funcDeclByPos map[token.Pos]*ast.FuncDecl) []binding {
bindings := make([]binding, len(serverMethods))
for i, fn := range serverMethods {
sig, _ := fn.Type().(*types.Signature)
Expand Down Expand Up @@ -133,13 +136,30 @@ func processServerMethods(serverMethods []*types.Func) []binding {
retType = "rpc2.StateOut"
}

docStr := ""

if decl := funcDeclByPos[fn.Pos()]; decl != nil && decl.Doc != nil {
docs := []string{}
for _, cmnt := range decl.Doc.List {
docs = append(docs, strings.TrimPrefix(strings.TrimPrefix(cmnt.Text, "//"), " "))
}

// fix name of the function in the first line of the documentation
if fields := strings.SplitN(docs[0], " ", 2); len(fields) == 2 && fields[0] == fn.Name() {
docs[0] = name + " " + fields[1]
}

docStr = strings.Join(docs, "\n")
}

bindings[i] = binding{
name: name,
fn: fn,
argType: sig.Params().At(0).Type().String(),
retType: retType,
argNames: argNames,
argTypes: argTypes,
docStr: docStr,
}
}
return bindings
Expand All @@ -159,8 +179,9 @@ func genMapping(bindings []binding) []byte {
fmt.Fprintf(buf, "// DO NOT EDIT: auto-generated using _scripts/gen-starlark-bindings.go\n\n")
fmt.Fprintf(buf, "package starbind\n\n")
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")
fmt.Fprintf(buf, "func (env *Env) starlarkPredeclare() starlark.StringDict {\n")
fmt.Fprintf(buf, "r := starlark.StringDict{}\n\n")
fmt.Fprintf(buf, "func (env *Env) starlarkPredeclare() (starlark.StringDict, map[string]string) {\n")
fmt.Fprintf(buf, "r := starlark.StringDict{}\n")
fmt.Fprintf(buf, "doc := make(map[string]string)\n\n")

for _, binding := range bindings {
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)
Expand Down Expand Up @@ -209,9 +230,24 @@ func genMapping(bindings []binding) []byte {
fmt.Fprintf(buf, "return env.interfaceToStarlarkValue(rpcRet), nil\n")

fmt.Fprintf(buf, "})\n")

// builtin documentation
docstr := new(strings.Builder)
fmt.Fprintf(docstr, "builtin %s(", binding.name)
for i, argname := range binding.argNames {
if i != 0 {
fmt.Fprintf(docstr, ", ")
}
fmt.Fprintf(docstr, argname)
}
fmt.Fprintf(docstr, ")")
if binding.docStr != "" {
fmt.Fprintf(docstr, "\n\n%s", binding.docStr)
}
fmt.Fprintf(buf, "doc[%q] = %q\n", binding.name, docstr.String())
}

fmt.Fprintf(buf, "return r\n")
fmt.Fprintf(buf, "return r, doc\n")
fmt.Fprintf(buf, "}\n")

return buf.Bytes()
Expand Down Expand Up @@ -301,14 +337,23 @@ func main() {
}

var serverMethods []*types.Func
funcDeclByPos := make(map[token.Pos]*ast.FuncDecl)
packages.Visit(pkgs, func(pkg *packages.Package) bool {
if pkg.PkgPath == "github.com/go-delve/delve/service/rpc2" {
serverMethods = getSuitableMethods(pkg.Types, "RPCServer")
}
for _, file := range pkg.Syntax {
ast.Inspect(file, func(n ast.Node) bool {
if n, ok := n.(*ast.FuncDecl); ok {
funcDeclByPos[n.Name.Pos()] = n
}
return true
})
}
return true
}, nil)

bindings := processServerMethods(serverMethods)
bindings := processServerMethods(serverMethods, funcDeclByPos)

switch kind {
case "go":
Expand Down
58 changes: 57 additions & 1 deletion pkg/terminal/starbind/starlark.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"io"
"io/ioutil"
"runtime"
"sort"
"strings"
"sync"

Expand All @@ -28,6 +29,7 @@ const (
dlvContextName = "dlv_context"
curScopeBuiltinName = "cur_scope"
defaultLoadConfigBuiltinName = "default_load_config"
helpBuiltinName = "help"
)

func init() {
Expand Down Expand Up @@ -71,7 +73,13 @@ func New(ctx Context, out EchoWriter) *Env {
// Make the "time" module available to Starlark scripts.
starlark.Universe["time"] = startime.Module

env.env = env.starlarkPredeclare()
var doc map[string]string
env.env, doc = env.starlarkPredeclare()

builtindoc := func(name, args, descr string) {
doc[name] = name + args + "\n\n" + name + " " + descr
}

env.env[dlvCommandBuiltinName] = starlark.NewBuiltin(dlvCommandBuiltinName, func(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
if err := isCancelled(thread); err != nil {
return starlark.None, err
Expand All @@ -90,6 +98,8 @@ func New(ctx Context, out EchoWriter) *Env {
}
return starlark.None, decorateError(thread, err)
})
builtindoc(dlvCommandBuiltinName, "(Command)", "interrupts, continues and steps through the program.")

env.env[readFileBuiltinName] = starlark.NewBuiltin(readFileBuiltinName, func(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
if len(args) != 1 {
return nil, decorateError(thread, fmt.Errorf("wrong number of arguments"))
Expand All @@ -104,6 +114,8 @@ func New(ctx Context, out EchoWriter) *Env {
}
return starlark.String(string(buf)), nil
})
builtindoc(readFileBuiltinName, "(Path)", "reads a file.")

env.env[writeFileBuiltinName] = starlark.NewBuiltin(writeFileBuiltinName, func(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
if len(args) != 2 {
return nil, decorateError(thread, fmt.Errorf("wrong number of arguments"))
Expand All @@ -115,12 +127,56 @@ func New(ctx Context, out EchoWriter) *Env {
err := ioutil.WriteFile(string(path), []byte(args[1].String()), 0640)
return starlark.None, decorateError(thread, err)
})
builtindoc(writeFileBuiltinName, "(Path, Text)", "writes text to the specified file.")

env.env[curScopeBuiltinName] = starlark.NewBuiltin(curScopeBuiltinName, func(_ *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
return env.interfaceToStarlarkValue(env.ctx.Scope()), nil
})
builtindoc(curScopeBuiltinName, "()", "returns the current scope.")

env.env[defaultLoadConfigBuiltinName] = starlark.NewBuiltin(defaultLoadConfigBuiltinName, func(_ *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
return env.interfaceToStarlarkValue(env.ctx.LoadConfig()), nil
})
builtindoc(defaultLoadConfigBuiltinName, "()", "returns the default load configuration.")

env.env[helpBuiltinName] = starlark.NewBuiltin(helpBuiltinName, func(_ *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
switch len(args) {
case 0:
fmt.Fprintln(env.out, "Available builtins:")
bins := make([]string, 0, len(env.env))
for name, value := range env.env {
switch value.(type) {
case *starlark.Builtin:
bins = append(bins, name)
}
}
sort.Strings(bins)
for _, bin := range bins {
fmt.Fprintf(env.out, "\t%s\n", bin)
}
case 1:
switch x := args[0].(type) {
case *starlark.Builtin:
if doc[x.Name()] != "" {
fmt.Fprintf(env.out, "%s\n", doc[x.Name()])
} else {
fmt.Fprintf(env.out, "no help for builtin %s\n", x.Name())
}
case *starlark.Function:
fmt.Fprintf(env.out, "user defined function %s\n", x.Name())
if doc := x.Doc(); doc != "" {
fmt.Fprintln(env.out, doc)
}
default:
fmt.Fprintf(env.out, "no help for object of type %T\n", args[0])
}
default:
fmt.Fprintln(env.out, "wrong number of arguments ", len(args))
}
return starlark.None, nil
})
builtindoc(helpBuiltinName, "(Object)", "prints help for Object.")

return env
}

Expand Down
Loading

0 comments on commit 7d8f476

Please sign in to comment.