How to debug a guile scheme codebase in emacs geiser repl?

I am trying to debug a guile scheme codebase. The codebase has the following files:

/home/user/Documents/takurtukur/tekuti:

base64.scm
boot.scm
cache.scm
classifier.scm
comment.scm
config.scm
filters.scm
git.scm
index.scm
marxdown.scm
match-bind.scm
mod-lisp.scm
page.scm
page-helpers.scm
post.scm
request.scm
tags.scm
template.scm
util.scm
web.scm

I also have a rough idea about the entry point and the which functions from which files get called:

boot function from boot.scm → ensure-git-repo from git.scm → call-with-temp-file from util.scm

I am trying to use guile repl inside emacs to insert a breakpoint in the boot function in the boot.scm file. I think I can use ,bp boot or ,bp ensure-git-repo commands in the repl for that. But before doing that I need to make sure that repl has loaded all these files and somehow starts executing the flow I descrubed above. I know how to add this directory to the load-path, and I do that. But after that, I don’t know how I can move on. I don’t wnt to write my own guile functions or edit the existing ones. I just want to juse repl to see the flow and check local variables. Where do I begin with this? I took hands on guile scheme for beginners course from @daviwil but that course showed me how to debug in the repl for a single code file loaded into the repl. In the case of this codebase, how does one begin?


I add the directory to the load-path:

scheme@(guile-user)> (display %load-path) (newline)
(~/Documents/takurtukur/ /usr/share/emacs/site-lisp/elpa/geiser-0.10/scheme/guile/ /usr/share/guile/3.0 /usr/share/guile/3.0 /usr/share/guile/site/3.0 /usr/share/guile/site /usr/share/guile)
scheme@(guile-user)> 

Now I try to add a breakpoint to the boot function in boot.scm file


scheme@(guile-user)> ,bp boot 
Trap 0: Breakpoint at #<procedure boot (args)>.
scheme@(guile-user)> (boot)
;;; <stdin>:904:0: warning: possibly wrong number of arguments to `boot'
Trap 0: Breakpoint at #<procedure boot (args)>
Entering a new prompt.  Type `,bt' for a backtrace or `,q' to continue.
scheme@(guile-user) [1]> ,bt
In current input:
     86:0  0 (boot)
scheme@(guile-user) [1]> ,locals
  No local variables.
scheme@(guile-user) [1]> ,step
Step into #<frame 73e1f1030100 boot>
scheme@(guile-user) [1]> ,bt
In current input:
     86:0  1 (boot _)
In ice-9/boot-9.scm:
   1754:4  0 (throw wrong-number-of-args #f "Wrong number of arguments to ~A" (#<proced…>) …)
scheme@(guile-user) [1]> 

But this is the wrong boot funcction? It seems like it has put a breakpoint to the boot function in ice-9 library?

With the help of chatgpt, I was able to enter into debugger in the repl using the following steps:

(guile repl)

(add-to-load-path "/home/user/Documents/takurtukur/")
(use-modules (tekuti boot))
(define tekuti-boot (@ (tekuti boot) boot)) ;; apparently I /have/ to do this
,bp tekuti-boot
((@ (tekuti boot) boot) (program-arguments))

From then on, I am in the breakpoint, and I can move forward with ,step or ,next, or ,finish repl commands. I can also examine the ,locals and check ,bt.

Some questions:

  1. Are these steps normal? Is this how it is done in using repl debugging with guile codebases?
  2. is the (define tekuti-boot (@ (tekuti boot) boot)) ;; apparently I /have/ to do this step normal? It seems too band-aid-like to me. I suspect I am doing something wrong.

I think you’re on the right track with debugging in the Guile REPL, but it seems like the REPL is not finding the boot function from your codebase and is instead resolving to the boot function from the ice-9 module. Here’s how you can address this issue step by step:

  1. Ensure Your Codebase Files Are Loaded

Since you’ve added the directory to %load-path, you need to load the files in the right order for your boot function to be available in the REPL. Typically, you would start by loading boot.scm, as it seems to be the entry point.

In the REPL:

scheme@(guile-user)> (load "boot.scm")

If boot.scm depends on other files (e.g., it uses functions from git.scm or util.scm), Guile will look for those in %load-path. Ensure that the directory containing these files is correctly listed in %load-path (which it seems to be).

  1. Verify the Right boot Function

After loading boot.scm, check that the boot function you want is available and distinct from the one in the ice-9 module:

scheme@(guile-user)> (procedure-source boot)

This will show you the source code or the location of the boot function currently in scope. Ensure it points to your boot.scm.

If it doesn’t, you may need to explicitly refer to the boot function from your module. For instance, if your file defines a module my-module:

scheme@(guile-user)> (use-modules (my-module))
scheme@(guile-user)> (procedure-source my-module:boot)
  1. Set a Breakpoint

Now that you’ve ensured the correct boot function is loaded, set a breakpoint:

scheme@(guile-user)> ,bp boot

or, if using the module-qualified name:

scheme@(guile-user)> ,bp my-module:boot
  1. Run the Code Flow

To start the flow, you’ll need to invoke the boot function in a way that matches its expected arguments. You mentioned an error with the wrong number of arguments; this means you need to call it with the correct signature.

Inspect the function definition in boot.scm:

scheme@(guile-user)> (procedure-documentation boot)

Use the documentation or look at the source file to determine the correct arguments. Then call the function, for example:

scheme@(guile-user)> (boot arg1 arg2 ...)
  1. Debugging Inside the Flow

Once the breakpoint is hit, you can use debugging commands like:

    ,bt for a backtrace
    ,locals to inspect local variables
    ,step to step through code execution
    ,q to continue running after the breakpoint

Additional Tips

If boot.scm defines a module (e.g., (define-module (my-module))), ensure you use (use-modules (my-module)) in the REPL to bring its definitions into scope.
If the boot function is not the top-level entry point, but is instead called by another function, you’ll need to start from the real entry point and let the flow naturally invoke boot.

Hope that this is of some help.

1 Like

In the repl:

(add-to-load-path "/home/user/Documents/takurtukur/") ;; success
(load "boot.scm") ;; fails
(load "tekuti/boot.scm") ;; success
(procedure-source boot) ;; fails
;;; <unknown-location>: warning: possibly unbound variable `boot'
ice-9/boot-9.scm:1685:16: In procedure raise-exception:
Unbound variable: boot

Entering a new prompt.  Type `,bt' for a backtrace or `,q' to continue.

Does the above error output mean I don’t have a boot function currently in scope?


Instead of this way, I also use the second way you write in your post: the (use-modules) way. However, the procedure-source stuff still gives an error, or some cryptic output (#f, below):

guile repl (inside emacs geiser stuff)

scheme@(guile-user)> (add-to-load-path "/home/user/Documents/takurtukur/")
scheme@(guile-user)> (use-modules (tekuti boot))
scheme@(guile-user)> (procedure-source boot:boot) ;; trying the boot function from tekuti/boot.scm file
;;; <unknown-location>: warning: possibly unbound variable `boot:boot'
ice-9/boot-9.scm:1685:16: In procedure raise-exception:
Unbound variable: boot:boot

Entering a new prompt.  Type `,bt' for a backtrace or `,q' to continue.
scheme@(guile-user) [1]> (procedure-source boot)
$2 = #f   ;; what does #f output mean here?  False?
scheme@(guile-user) [1]> 

This also doesn’t work, and I would like to get it working:

guile repl again

scheme@(guile-user) [1]> ,bp boot:boot
;;; <unknown-location>: warning: possibly unbound variable `boot:boot'
While executing meta-command:
Unbound variable: boot:boot
scheme@(guile-user) [1]> ,bp boot.scm:boot
;;; <unknown-location>: warning: possibly unbound variable `boot.scm:boot'
While executing meta-command:
Unbound variable: boot.scm:boot
scheme@(guile-user) [3]> ,bp tekuti/boot.scm:boot
;;; <unknown-location>: warning: possibly unbound variable `tekuti/boot.scm:boot'
While executing meta-command:
Unbound variable: tekuti/boot.scm:boot

Not a Guile developer, but those step don’t seem normal. Could you please post your boot.scm, including the define-module? It would be great if you add just enough code so that others can reproduce the issue on their own installation.

Hey @ashraz you can find the repo I am working with here: GitHub - k4r4b3y/takurtukur: My fork of wingo's tekuti, a blog engine written in scheme. – checkout the tekuti/ directory.

Sorry for the late reply. Let me again preface this with saying that I have no idea what I’m doing here (not a Guile nor Guix user), but:

You’re following a red herring with the procedure-source there. The fact that you’re getting #f shows that it is a procedure, otherwise you would get a type error:

(define x 1)
(procedure-source x) ; <=  In procedure procedure-source: Wrong type argument in position 1: 1

You can verify that you have boot already at hand by just using procedure?:

(use-modules (tekuti boot))
(procedure? boot) ; => #t

To add a breakpoint to boot just use ,bp boot:

GNU Guile 3.0.8
Copyright (C) 1995-2021 Free Software Foundation, Inc.

Guile comes with ABSOLUTELY NO WARRANTY; for details type `,show w'.
This program is free software, and you are welcome to redistribute it
under certain conditions; type `,show c' for details.

Enter `,help' for help.
scheme@(guile-user)> (add-to-load-path "/home/ashraz/workspace/helpdesk/systemcrafters/takurtukur")
scheme@(guile-user)> (use-modules (tekuti boot))
scheme@(guile-user)> (procedure? boot)
$6 = #t
scheme@(guile-user)> ,bp boot
Trap 0: Breakpoint at #<procedure boot (args)>.

To add a breakpoint to a non-exported procedure, use (@@ module-name binding-name):

scheme@(guile-user)> ,bp (@@ (tekuti boot) parse-options)
Trap 1: Breakpoint at #<procedure parse-options (args)>.

Then just run your code:

scheme@(guile-user)> (boot #f) ; <= wrong argument, but let's see the breakpoints
Trap 0: Breakpoint at #<procedure boot (args)>
Entering a new prompt.  Type `,bt' for a backtrace or `,q' to continue.
scheme@(guile-user) [1]> ,bt
In tekuti/boot.scm:
     72:0  0 (boot #f)
scheme@(guile-user) [1]> ,q
Trap 1: Breakpoint at #<procedure parse-options (args)>
Entering a new prompt.  Type `,bt' for a backtrace or `,q' to continue.
scheme@(guile-user) [1]> ,bt
In tekuti/boot.scm:
    74:17  1 (boot _)
     56:0  0 (parse-options #f)
scheme@(guile-user) [1]> ,finish
ice-9/boot-9.scm:1685:16: In procedure raise-exception:
In procedure car: Wrong type argument in position 1 (expecting pair): #f

Entering a new prompt.  Type `,bt' for a backtrace or `,q' to continue.
scheme@(guile-user) [1]> ,bt
In tekuti/boot.scm:
    74:17  3 (boot _)
    57:14  2 (parse-options _)
In ice-9/getopt-long.scm:
   341:31  1 (getopt-long _ _ #:stop-at-first-non-option _)
In ice-9/boot-9.scm:
  1685:16  0 (raise-exception _ #:continuable? _)
scheme@(guile-user) [1]> ,q

Just the scheme code in case you want to reproduce this in your own REPL:

(add-to-load-path "/home/ashraz/workspace/helpdesk/systemcrafters/takurtukur")
(use-modules (tekuti boot))
(procedure? boot)

And the breakpoints:

,bp boot
,bp (@@ (tekuti boot) parse-options) ; the @@ is required as parse-options is not exported

By the way, if you really want to prefix your tekuti functions, then use #:prefix in use-modules:

scheme@(guile-user)> (add-to-load-path "/home/ashraz/workspace/helpdesk/systemcrafters/takurtukur")
scheme@(guile-user)> (use-modules ((tekuti boot) #:prefix tekuti:))
scheme@(guile-user)> (procedure? tektui:boot)
$3 = #t

Hope that helps a bit.


By the way, boot is a horrible function to inspect. It doesn’t have any local variables, unless you provide a valid configuration, so expect your ,locals to always be empty.

You are right about this part. Here’s me confirming you step by step in the guile repl in emacs:

scheme@(guile-user)> (display %load-path) (newline) ;; display the default %load-path
(/usr/share/emacs/site-lisp/elpa/geiser-0.10/scheme/guile/ /usr/share/guile/3.0 /usr/share/guile/3.0 /usr/share/guile/site/3.0 /usr/share/guile/site /usr/share/guile)
scheme@(guile-user)> (add-to-load-path "/home/user/Documents/takurtukur/") ;; add project dir to %load-path
scheme@(guile-user)> (display %load-path) (newline) ;; display the modified %load-path
(/home/user/Documents/takurtukur/ /usr/share/emacs/site-lisp/elpa/geiser-0.10/scheme/guile/ /usr/share/guile/3.0 /usr/share/guile/3.0 /usr/share/guile/site/3.0 /usr/share/guile/site /usr/share/guile)
scheme@(guile-user)> (use-modules (tekuti boot)) ;; load tekuti/boot.scm
scheme@(guile-user)> (procedure? boot)
$2 = #t ;; success
scheme@(guile-user)> 

This works as well. Question: since the takurtukur is first in the %load-path does that mean the ,bp in the repl command acts on the boot function in the tekuti/boot.scm file? Because there is at least one other boot function in the ice-9 library.

Yes, this works as well!

scheme@(guile-user)> ,bp boot
Trap 0: Breakpoint at #<procedure boot (args)>.
scheme@(guile-user)> (boot program-arguments)
Trap 0: Breakpoint at #<procedure boot (args)>
Entering a new prompt.  Type `,bt' for a backtrace or `,q' to continue.
scheme@(guile-user) [1]> ,bt
In tekuti/boot.scm:
     72:0  0 (boot #<procedure program-arguments ()>)
scheme@(guile-user) [1]> 

This was a great help. Really cleared my beginner level confusions around simple guile project debugging. Thank you.

If the (use-modules (tekuti boot)) was the last use-modules, then I guess it’s likely that boot is (@ (tekuti boot) boot). You can use the #:prefix to differentiate both of them.

Either way, ,bp boot will add a breakpoint to the current boot. If you change that later on, the breakpoint will vanish:

scheme@(guile-user)> (define (example) (display "Example") (newline))
scheme@(guile-user)> ,bp example
Trap 0: Breakpoint at #<procedure example ()>.
scheme@(guile-user)> (example)
Trap 0: Breakpoint at #<procedure example ()>
Entering a new prompt.  Type `,bt' for a backtrace or `,q' to continue.
scheme@(guile-user) [1]> ,q
Example
scheme@(guile-user)> (define (example) (display "Breakpoint gone") (newline))
scheme@(guile-user)> (example)
Breakpoint gone

But again, maybe some experienced guile user can chime in, as I’m only paraphrasing a bit of documentation and speaking of experience in other dynamic languages :sweat_smile:.