Debugging

There are two major ways to debug functions (and methods) at runtime: Tracing and single-stepping.

Tracing means letting functions of interest print their name and arguments when they are entered, and their name again and the return value when they are exited.

For demonstration, let's define the unavoidable factorial function:
(de fact (N)
   (if (=0 N)
      1
      (* N (fact (- N 1))) ) )
With trace we can put it in trace mode:
   : (trace 'fact)
   -> fact
Calling 'fact' now will display its execution trace.
   : (fact 3)
    fact : 3
     fact : 2
      fact : 1
       fact : 0
       fact = 1
      fact = 1
     fact = 2
    fact = 6
   -> 6
As can be seen here, each level of function call will indent by an additional space. Upon function entry, the name is separated from the arguments with a colon (':'), and upon function exit with an equals sign ('=') from the return value.

'trace' works by modifying the function body, so generally only for functions defined as lists (lambda expressions, see Evaluation. Tracing a C-function is possible, however, when it is a function that evaluates all its arguments.

So let's trace the functions =0 and *:
   : (trace '=0)
   -> =0
   : (trace '*)
   -> *
If we call 'fact' again, we see the additional output:
   : (fact 3)
    fact : 3
     =0 : 3
     =0 = NIL
     fact : 2
      =0 : 2
      =0 = NIL
      fact : 1
       =0 : 1
       =0 = NIL
       fact : 0
        =0 : 0
        =0 = 0
       fact = 1
       * : 1 1
       * = 1
      fact = 1
      * : 2 1
      * = 2
     fact = 2
     * : 3 2
     * = 6
    fact = 6
   -> 6
To reset a function to its untraced state, call untrace
   : (untrace 'fact)
   -> fact
   : (untrace '=0)
   -> =0
   : (untrace '*)
   -> *
or simply
   : (mapc untrace '(fact =0 *))
   -> *
Single-stepping means to execute a function step by step, giving the programmer an opportunity to look more closely at what is happening. The function debug inserts a breakpoint into each top-level expression of a function. When the function is called, it stops at each breakpoint, displays the expression it is about to execute next (this expression is also stored into the global variable ^)

and enters a read-eval-loop. The programmer can then Thus, in the simplest case, single-stepping consists of just hitting ENTER repeatedly to step through the function.

To try it out, let's look at the stamp system function.
   : (pp 'stamp)
   (de stamp (Dat Tim)
      (and (=T Dat) (setq Dat (date T)))
      (default Dat (date) Tim (time T))
      (pack (dat$ Dat "-") " " (tim$ Tim T)) )
   -> stamp
   : (debug 'stamp)                       # Debug it
   -> T
   : (stamp)                              # Call it again
   (and (=T Dat) (setq Dat (date T)))     # stopped at first expression
   !                                      # ENTER
   (default Dat (date) Tim (time T))      # second expression
   !                                      # ENTER
   (pack (dat$ Dat "-") " " (tim$ ...     # third expression
   ! Tim                                  # inspect 'Tim' variable
   -> 41908
   ! (time Tim)                           # convert it
   -> (11 38 28)
   !                                      # ENTER
   -> "2004-10-29 11:38:28"               # done, as there are only 3 expressions
Now we execute it again, but this time we want to look at what's happening inside the second expression.
   : (stamp)                              # Call it again
   (and (=T Dat) (setq Dat (date T)))
   !                                      # ENTER
   (default Dat (date) Tim (time T))
   !                                      # ENTER
   (pack (dat$ Dat "-") " " (tim$ ...     # here we want to look closer
   ! (d)                                  # debug this expression
   -> T
   !                                      # ENTER
   (dat$ Dat "-")                         # stopped at first subexpression
   ! (e)                                  # evaluate it
   -> "2004-10-29"
   !                                      # ENTER
   (tim$ Tim T)                           # stopped at second subexpression
   ! (e)                                  # evaluate it
   -> "11:40:44"
   !                                      # ENTER
   -> "2004-10-29 11:40:44"               # done
The breakpoints still remain in the function body. We can see them when we pretty-print it:
   : (pp 'stamp)
   (de stamp (Dat Tim)
      (! and (=T Dat) (setq Dat (date T)))
      (! default Dat (date) Tim (time T))
      (! pack
         (! dat$ Dat "-")
         " "
         (! tim$ Tim T) ) )
   -> stamp
To reset the function to its normal state, call
   : (unbug 'stamp)
Often, you will not want to single-step a whole function. Just use 'edit' (see above) to insert a single breakpoint (the exclamation mark followed by a space) as CAR of an expression, and run your program. Execution will then stop there as described above; you can inspect the environment and continue execution with ENTER when you are done.

https://picolisp.com/wiki/?tutdbg

01nov10    abu