Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ClojureScript support to the debugger #1416

Open
Malabarba opened this issue Nov 13, 2015 · 14 comments
Open

Add ClojureScript support to the debugger #1416

Malabarba opened this issue Nov 13, 2015 · 14 comments

Comments

@Malabarba
Copy link
Member

@Malabarba Malabarba commented Nov 13, 2015

The debugger simply fails if used in ClojureScript code, it would be nice for it to work.

Here's a list of things to achieve that. If anyone can help with any of these items, it will likely save me a lot of researching down the road. So please don't be shy.

  • (trivial) Change the debug middleware from cljs/expects-piggieback to cljs/requires-piggieback.
  • Ensure that the #dbg and #break reader tags are active when the cljs code is read. This is done in Clojure via our data_readers.clj file at the classpath root, is there a similar file for ClojureScript?
  • Currently instrumentation is done by ensuring that tools.nrepl uses our instrument-and-eval function instead of the plain eval (see debug.clj). I'm not sure how to do this in piggieback.
  • instrument.clj should work as is. It parses the code (which is still Clojure data even with cljs code), and it wraps parts of it in some code of our own.
  • The debugger then works by running some CIDER code inside the user's code. This code therefore needs to be defined in the cljs environment.

The last item is hardest. The code in question is just one macro and a few functions in debug.clj. These needs to be defined in the cljs environment, which probably means they'll have to be moved to a cljc file (or duplicated in a cljs file).

The problem is that this code interacts with tools.nrepl, which (IIUC) is impossible to do from the cljs environment. So it'll likely involved a deep rethinking of how the debugger works.
Reimplementing the first version of the debugger in cljs might be the solution (it prompted the user directly instead of using the nrepl).

@divs1210
Copy link

@divs1210 divs1210 commented Feb 21, 2016

Hey, @Malabarba ! Thanks for the list of things to start off from. I'm new to Emacs and CIDER, but would like to give this a shot.

@sfrdmn
Copy link

@sfrdmn sfrdmn commented Aug 4, 2016

Any thoughts on a setting for simply ignoring the fact you're in a CLJS file and just interpreting the forms as plain Clojure? I guess it'll work fine in a lot of cases and if it's documented + opt-in, shouldn't be too problematic?

@Malabarba
Copy link
Member Author

@Malabarba Malabarba commented Aug 4, 2016

I think M-x clojure-mode might work.

@sfrdmn
Copy link

@sfrdmn sfrdmn commented Aug 4, 2016

So it does! Thanks 👍

zlrth added a commit to getsparket/first-fly that referenced this issue May 21, 2017
made the finance.clj a cljc
because cljs files can't be debugged with
cider's debugger.
clojure-emacs/cider#1416
it will probably be used exclusively on the cljs side
as we have no clj backend or anything now.
@crinklywrappr
Copy link

@crinklywrappr crinklywrappr commented Mar 1, 2018

2nd time this week Cider has prompted me to visit this page.

@NightMachinary
Copy link

@NightMachinary NightMachinary commented May 8, 2018

How does Cursive manage this?

@bbatsov
Copy link
Member

@bbatsov bbatsov commented May 8, 2018

No idea. It's not open-source so we can't easily check.

@cursive-ide
Copy link

@cursive-ide cursive-ide commented May 8, 2018

@NightMachinary @bbatsov Very simple - it doesn't :-). Cursive can only debug JVM clojure right now.

@bendlas
Copy link
Contributor

@bendlas bendlas commented Jun 12, 2018

So, the biggest problem with having a debugger based on instrumented code in JavaScript, is, that unlike the JVM, you can't block your thread to wait for input from the debugging frontend. This leaves two likely approaches: Debugging via the runtime's debugger interface (e.g. via debugger.html) and relying on source maps to recover the cider experience, or, doing a CPS transformation on the source.

I honestly can't decide, what feels to be more work, but ultimately, CPS feels more in-line with the spirit in the existing debugger, of instrumenting the source. A remote-control debugger would totally be worth doing, though, and I'm sure that an RDP-based debugger, targetting Clojure and ClojureScript could be a great success as well.

That said, let me try to sell you on an even crazier idea, than doing CPS on ClojureScript code: Doing CPS on JavaScript code. That would (theoretically) enable debugging callbacks coming from javascript frameworks.

I decided to explore existing CPS solutions for JavaScript, and there are some, most of them seem to focus on providing some syntax for explicit passing, but jwacs stood out, not just because it takes its job of doing the heavy lifting for providing true continuations, seriously, but also because it's written in CommonLisp, so working with it reminded me of how Clojure with Emacs should feel ;-)

Initial results on some snippets and even a 2.4M advanced minified JS seemed promising, after fixing some minor issues

I so far found three features missing:

  • finally clauses are documented as missing chumsley/jwacs#4
  • geters, seters, necessary to deal with in some way, because they can't be transpiled away chumsley/jwacs#5
  • breaks from labelled-statement-blocks, a rather obscure language feature, used by gclosure advanced

After implementing labelled statement blocks to get acquainted with the code base, I'm pretty confident, that I (and you too) can make it work for everything we need, including the above issues.

If you're interested in helping with this, I'm currently in the process of getting acquainted with cider-debug-middleware's internals. In particular, I'd like to figure out, how to run some hand-crafted code in a clojurescript runtime, to contact cider-debug-middleware with a break-point.

TLDR;
A jwacs-based cps-transformer could be run in a separate server, processing JavaScript, instrumented with breakpoints based on function_continuation, suspend and resume, as per jwacs-doc.

@bbatsov
Copy link
Member

@bbatsov bbatsov commented Jun 17, 2018

@bendlas It's nice to see someone interested in fixing this! Let me know if you need any assistance from me!

I don't know almost anything about ClojureScript, but I know a thing or two about CIDER. :-)

@bendlas
Copy link
Contributor

@bendlas bendlas commented Jun 17, 2018

@bbatsov theat's great, thanks for offering!
I'm currently trying to understand how piggieback communicates with the rest of nrepl. For some reason, on a piggieback-ed cljs connection, I don't see any more messages coming through the debug middleware ... we can discuss more interactively on the #cider slack ...

@divs1210
Copy link

@divs1210 divs1210 commented Dec 3, 2018

This could be helpful in some way: https://github.com/philoskim/debux

@stale
Copy link

@stale stale bot commented May 8, 2019

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contribution and understanding!

@stale stale bot added the stale label May 8, 2019
@stale stale bot removed the stale label May 11, 2019
@rksm
Copy link
Contributor

@rksm rksm commented Aug 19, 2019

These are my 2¢ from a recent Slack chat, @bbatsov suggested to post these for posteriors' sake.

The comment that @bendlas left last year is spot on about how the last item in the "todo list" can be approached. However having done a JS debugger using the CPS approach myself once, I can say that this approach is more complicated as it seems. Again, the fundamental problem is you can't stop / blockingly wait in JavaScript. What you basically do to get around that is to a) instrument all code so that it can record and externalize stackframes / state on the stackframes and some sort of handmade PC (program counter) so you know where you are. This part is actually not that hard except for one issue: it requires instrumentation of ALL code, at least all code that can call code you want to debug -> so you have the stackframes to step/continue. JS has the concept of native funcions (e.g. iterators) that might very well call your code so this is the tricky part about it. There are ways to hack around that issue.

"Stopping" happens by unwinding the program using exceptions, that, step-by-step record the state that is present b/c of the instrumented code. This is easy.Problem three to solve is how to resume execution. The state you gathered in step 2 is all you need, but there is no built-in method to resume, of course. In the past I've used a JavaScript-in-JavaScript interpreter to "fast-forward" to the position in the stack frames and then run natively from there on. Again, this needs instrumentation of all code leading to the code you want to debug. Instead of an interpreter one can use a "skip" implementation similar to what the cider debugger uses right now, I think this would be a bit nicer.

Let's say this all can be build, the remaining issue is to integrate the code rewriting with the ClojureScript compiler itself. Ideally the rewriting should happen as part of the compilation step.From my humble experience this is a whole lot of pain and more time intensive than a voluntary project will allow.

So there are two alternatives, I think. On the one hand, it would be possible to do a "CPS-light" debugger, just for a single function, maybe with a step into for code that gets called. That would only require to instrument code that the user actually wants to debug but you can't access the stack. I haven't thought too deeply about that and there might be issues I overlook but I think that could be made to work reasonably fast.

The other solution is to use Chrome DevTools Protocol to remote control a chrome / v8 vm, e.g. via https://github.com/tatut/clj-chrome-devtools. The advantage of this approach is that it definitely works and since devtools are very widely used now will remain pretty stable. The only problem I see is that devtools are JS not CLJS tooling. This means, even with the use of source maps, there will be a certain mismatch between what the chrome debugger actually works on and what the user has written. E.g. Certain local vars made not be accessible, the debugger does not know about macros and such. Other than that the effort to integrate that is to make it work with the bazillions of ways of using ClojureScript. What's basically needed there is to create a "side-channel" browser environment that connects to the chrome-dev instance (similar to https://github.com/hagmonk/cobalt but prepped for debugging tasks). It needs to share the compiler / browser state with the tool the user actually uses, e.g. figwheel or shadow or whatever. So the main work is in getting those configs right, "multiplex" the repl and keep everything in sync

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
9 participants