go-delve / delve

Delve is a debugger for the Go programming language.
MIT License
22.79k stars 2.14k forks source link

Support a scripting language #1415

Closed dgryski closed 5 years ago

dgryski commented 5 years ago

Consider this a long term, large-scale feature request.

Inspired by this CppCon talk “Liberating the Debugging Experience with the GDB Python API”, I think delve should support a rich scripting interface.

The GDB docs have a more detailed overview of the Python API.

There are a number of scripting languages that could be used instead of Python: javascript, starlark, or one of the many lua implementations.

aarzilli commented 5 years ago

Consider this a long term, large-scale feature request.

quite so.

aarzilli commented 5 years ago

I've taken a look at those interpreters as well as the ones listed at awesome-go and found nothing suitable for delve.

My criteria were:

  1. A well-known language
  2. Not using cgo
  3. Capable of representing 64bit integers

Criterion (1) rules out agora, anko, expr, ngaro and glisp. Criterion (2) rules out aarzilli/golua, go-php, go-python and purl. Criterion (3) rules out otto (javascript) and all lua implementations. This leaves us with starlark which would work if it weren't so limited as a scripting language (no filesystem access, no while statement, no mutable global variables, no if/for outside of a function...).

dgryski commented 5 years ago

I agree with your criteria and your assessment of the currently available options. I guess we'll wait until something more appropriate comes along.

Thanks for looking into this.

dgryski commented 5 years ago

I wonder if there are any plans to expand starlark's scripting capabilities or if the language is effectively frozen?

/cc @alandonovan

alandonovan commented 5 years ago

This leaves us with starlark which would work if it weren't so limited as a scripting language (no filesystem access, no while statement, no mutable global variables, no if/for outside of a function...).

File system access is easily solved by adding new built-in functions to a special module; see the assert module, or the sketch of the time package https://github.com/google/skylark/tree/wip-skylark-time. If you need deterministic or secure/hermetic execution you would need to consider this in the design of the new functions.

I would be consider accepting a change (behind a global flag) to make the interpreter accept arbitrary while loops (using the for keyword) and recursive function calls. There are many potential clients for Starlark-Go that such a change would enable, even if a Bazel-like build system continues to impose this restriction. (Frankly I think non-Turing-completeness is overrated: a bounded program may still consume all available time and memory. But the Bazel team assures me that in practice Turing completeness acts as a brake on users attempting inappropriate things during the build).

Mutable global variables are more problematic. Of course it's easy to simply skip the "freeze" step at the end of module execution, which would allow you to modify global data structures by saying load(..., "x"); x[0] = y. This of course undermines one of the key safety invariants for Skylark in a parallel program. You'd have to think through the consequences for your application. Even so, there would still be no way to assign directly to the variable x in one module and have other module see the effect; you'd need to box up the value in a list of length one. I'm open to suggestions for changes that don't hurt existing use-cases.

Re: No for/if outside a function: again, this restriction could in principle be lifted (behind a global flag) but the consequences would require careful thinking through. I would prefer to make this restriction a feature of the Bazel dialect---it already enforces a number of checks such as no def in BUILD files, only .bzl files, and they seem to belong together. [Update: it would be a safe change and wouldn't need a flag; filed upstream at bazelbuild/starlark#17]

In short: I would like Starlark-in-Go to support exactly the kind of scripting needs that Delve has, so long as we can do it without unduly complicating the build use-case.

aarzilli commented 5 years ago

File system access is easily solved by adding new built-in functions to a special module (...)

Well, the problem is not that it can't be solved but that I don't want to write and maintain a standard library for starlark.

Mutable global variables are more problematic (...)

Re: No for/if outside a function (...)

We could probably ask users to write their code inside a main function, instead of toplevel, and then call it automatically.

PaluMacil commented 5 years ago

While I haven't thought much about this overall topic, I think starlight-go/starlight might be more open to maintaining a standard library for Starlark since its purpose is to wrap the config-centric starlark into something more meant as a general purpose embeddable scripting language. It certainly looks like it simplifies a lot of the datatype conversion. I haven't used it, but I would like to when I get some time.

natefinch commented 5 years ago

Howdy... I am developing https://github.com/starlight-go/starlight which does not make a standard library for starlark, it lets you use Go's standard library for starlark. That is... you can pass basically any arbitrary Go type, functions etc into starlark and use them with zero work from you. It's all done through reflection, wrapping the types with a starlark compatibility layer.

I do plan to make simple wrappers for the go standard library so that it's easy to give a starlark script as much access to the standard library as you want (the wrappers would just be a struct that had methods that forward to each package function, and a field for each package variable, so no actual "logic" to maintain)

The for/if outside a function is really Not A Thing. You start the file with def do(): and end it with do(). Given that, I don't really see the point of the restriction, and would be in favor of being able to turn it off with a flag. Same with while loops and recursion, though to be honest, I don't actually use recursion very often (while loops definitely are useful).

alandonovan commented 5 years ago

The for/if outside a function is really Not A Thing.

It will soon be literally Not A Thing; see https://github.com/google/starlark-go/pull/77.

I am developing https://github.com/starlight-go/starlight

I really like what you've done with Starlight; it has a lot of potential to enable rich scripting integration for Go. I would consider adding something like this as a go.starlark.net package, which should make adoption simpler for most users. I'd first like to evaluate a slightly different implementation that I think might more cleanly handle several subtle issues of aliasing and addressability at the language boundary---this is always the most tricky part of any foreign-function interface.

natefinch commented 5 years ago

I would consider adding something like this as a go.starlark.net package, which should make adoption simpler for most users. I'd first like to evaluate a slightly different implementation that I think might more cleanly handle several subtle issues of aliasing and addressability at the language boundary---this is always the most tricky part of any foreign-function interface.

That sounds super cool. I'd love to hear what you have up your sleeve. Honestly, I don't care about the specific implementation as long as the UX stays mostly the same - i.e. you can easily pass in some "go stuff" and starlark can use it, manipulate it, and changes get reflected (heh) in the Go values.

I've definitely learned a lot in writing Starlight, it's my first major foray into reflection (and quite a thing, given that I have to basically write support for every single go language feature).

I am certain the code could be improved upon, and as I write tests for more complicated types, I have starting to hit the edge cases that probably involve what you're talking about.

aarzilli commented 5 years ago

This is all very cool. I have a working prototype of starlark integrated into delve already finished; but seeing that Derek is in vacation and the delve 1.2.0 deadline (i.e. go 1.12rc1) is approaching, it'll have to wait until after that.

dgryski commented 5 years ago

That's amazing! I wasn't expecting this to happen so quickly. Can wait to play with it.

glycerine commented 5 years ago

Starlark is pretty neat. I like it but I also already miss all the niceties of Go. How about Go as a scripting language, as in https://github.com/cosmos72/gomacro.

The only drawback is the MPL2 license. If enough of us ask the author nicely, perhaps he'll adopt a more community friendly license.

glycerine commented 5 years ago

@aarzilli Hi Alessandro, in your prototype of starlark integration, where you able to figure out how to make global variables mutable? It seems the resolver thinks that g is local in this example

>>> g=1
>>> def f(): g=g+1

>>> f()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in f
UnboundLocalError: local variable 'g' referenced before assignment
>>>

For a configuration language that makes sense, but for a scripting language it is a non-starter; plenty of communication with the host code will have to happen through globals. (this is with resolve.AllowGlobalReassign = true, and that prototype PR merged... https://github.com/google/starlark-go/pull/77 )

alandonovan commented 5 years ago

UnboundLocalError: local variable 'g' referenced before assignment

Your example behaves the same way in Python: both uses of 'g' inside the function refer to the local variable, because there exists a local binding of g. The important difference between the languages is that Python provides a 'global' statement (and a similar 'nonlocal' statement for nested defs) that allows you to say "within this function I mean the outer g".

There's no technical reason such a statement couldn't be easily added to Starlark, but I suspect the Bazel team has no need for it. Very few functions in the build system rebind global variables, not least because most functions run after module initialization, when their globals have been frozen.

glycerine commented 5 years ago

Yes, configuration doesn't need mutable globals, but scripting and debugging benefit greatly.

In addition to communicating with the host debugger, the other reason why mutable globals are very valuable during debugging is that they let you pretend to be inside your function, and step-through-your code line-by-line -- and thus debug it -- at the REPL toplevel. Critically, one can step line by line without having to re-write your lines in order to do so. The experience is like working at the python prompt or in a jupyter notebook doing data analysis.

aarzilli commented 5 years ago

First implementation of this available at https://github.com/go-delve/delve/pull/1466, code reviews / comments welcome.

The one important design principle here is that I don't want this feature to become a tamagotchi: it needs to look after itself. Because of this most of the work needs to be done either through auto-generated code or through reflection so that the starlark API will track any change we make to the JSON-RPC API without having to explicitly worry about keeping it up to date or maintaining backwards compatibility (i.e. we are already worrying about JSON-RPC API's backwards compatibility so the starlark API needs to be autogenerated from that in such a way that it will automatically stay backwards compatible too).

dgryski commented 5 years ago

Should this be closed?

derekparker commented 5 years ago

@dgryski yes :)