DAP Backed Toplevel

Version 0.4.0 of package debug_adapter is out and available via:

?- pack_install(debug_adapter)

This release features a new mode of operation which provides an integrated Prolog top-level.
First a screenshot for demonstration:


The way it works is that the DAP server starts a new thread running prolog/0 with hooks in place for controlling debugging facilities, and standard I/O streams redirected to a TCP socket. The server then sends a DAP request to the IDE instructing it to launch a terminal and connect it to the newly created Prolog top-level via TCP.
I find this mode of operation provides quite a nice development experience as you get both the interactive top-level and visual debugging features together in the IDE.

To start the integrated top-level using GNU/Emacs and dap-mode:

  1. Add the following lines to your init.el (won’t be needed once this PR to dap-mode is merged):
(dap-register-debug-template "SWI-Prolog Start Terminal"
                             (list :type "swi-prolog"
                                   :goal "$run_in_terminal"
                                   :request "launch"
                                   :name "SWI-Prolog::Terminal"))
  1. Type M-x dap-debug
  2. Choose “SWI-Prolog Start Terminal” when prompted for a debug configuration:
    Screen Shot 2022-02-19 at 16.15.25

A new buffer should open up showing the familiar ?- prompt.
Each time the tracer is hit, the DAP server notifies the IDE that the running thread stopped, and allows the IDE to request debug information like the current stack trace, the exact position in the source code (including generated sources for dynamic predicates), and variable bindings.

1 Like

Last week I wrote about starting a DAP-enabled Prolog top-level from within the IDE, but what if you prefer using the regular terminal-based top-level and only beginning the graphical tracer just-in-time when a thread is stopped? That’s a common workflow facilitated in SWI-Prolog with the built-in graphical debugger using gtrace/0.

The new version 0.5.0 of the debug adapter package supports this use case as well:
Loading library(debug_adapter/tracer) in the top-level will install a tracer hook that starts a user-defined DAP client (e.g. Emacs) and attaches it to the current Prolog thread.

How to specify a DAP client

The user specifies how the DAP client should be started by defining the multifile predicate user:debugger_connection_template(-Template).
For Emacs with dap-mode one can use the following definition:

user:debugger_connection_template("emacs --eval '(dap-debug (list :type \"swi-prolog-tcp\" :debugServer ~w))' &").

The template string is interpolated with the TCP port that the DAP server binds to using format/3 as:

format(string(Command), Template, [DapServerPort])

When the tracer starts and the running thread stops, Command will be executed to start the IDE, which then uses the newly created DAP session to fetch the state of the stopped thread and display it to the user, including which file to open and in what position.