What is a fast way to set debugging code at a given line in a function?

debugging, r

Solution

Here is a convenient wrapper for detecting the piece:

library(codetools)
ff <- function(f, tar) {
  cc <- function(e, w) {
    if(length(w$pos) > 0 &&
      grepl(w$tar, paste(deparse(e), collapse = "\n"), fixed = TRUE)) {
      cat(rev(w$pos), ": ", deparse(e), "\n")
      w$ret$vals <- c(w$ret$vals, list(rev(w$pos)))
    }
    w$pos <- c(0, w$pos)
    for (ee in as.list(e)){
      if (!missing(ee)) {      
        w$pos[1] <- w$pos[1] + 1
        walkCode(ee, w)
      }
    }
  }

  w <- list(pos = c(),
            tar = tar,
            ret = new.env(),
            handler = function(v, w) NULL,
            call = cc,
            leaf = function(e, w) NULL)
  walkCode(body(f), w = w)
  w$ret$vals
}

and then,

> r <- ff(pretty.default, "delta <- diff(range(z$l, z$u))/z$n")
12 :  if (!eps.correct && z$n) {     delta <- diff(range(z$l, z$u))/z$n     if (any(small <- abs(s) < 1e-14 * delta))          s[small] <- 0 } 
12 3 :  {     delta <- diff(range(z$l, z$u))/z$n     if (any(small <- abs(s) < 1e-14 * delta))          s[small] <- 0 } 
12 3 2 :  delta <- diff(range(z$l, z$u))/z$n 
> r
[[1]]
[1] 12

[[2]]
[1] 12  3

[[3]]
[1] 12  3  2

> r <- ff(model.frame.default, "stop(gettextf(\"factor '%s' has new level(s) %s\", nm, paste(nxl[m],")
26 3 2 4 3 4 4 4 3 :  stop(gettextf("factor '%s' has new level(s) %s", nm, paste(nxl[m],      collapse = ", ")), domain = NA) 
> r
[[1]]
[1] 26  3  2  4  3  4  4  4  3

and you can define the tracer by contents:

traceby <- function(fun, tar, cer) {
  untrace(deparse(substitute(fun)))
  r <- ff(fun, tar)
  r <- r[which.max(sapply(r, length))]
  trace(what = deparse(substitute(fun)), tracer = cer, at = r)
}

then,

> traceby(pretty.default, "if (any(small <- abs(s) < 1e-14 * delta)) s[small] <- 0", quote(cat("\nThe value of delta is: ", delta, "\n\n")))
Untracing function "pretty.default" in package "base"
12 3 3 :  if (any(small <- abs(s) < 1e-14 * delta)) s[small] <- 0 
Tracing function "pretty.default" in package "base"
[1] "pretty.default"
> a <- pretty(c(1, 7843))
Tracing pretty.default(c(1, 7843)) step 12,3,3 

The value of delta is:  2000 

> b <- pretty(c(2, 23))
Tracing pretty.default(c(2, 23)) step 12,3,3 

The value of delta is:  5 

Problem

Preamble: R's `trace()` is a powerful debugging tool, allowing users to "insert debugging code at chosen places in any function". Unfortunately, using it from the command-line can be fairly laborious. As an artificial example, let's say I want to insert debugging code that will report the between-tick interval calculated by `pretty.default()`. I'd like to insert the code immediately after the value of `delta` is calculated, about four lines up from the bottom of the function definition. (Type `pretty.default` to see where I mean.) To indicate that line, I need to find which step in the code it corresponds to. The answer turns out to be step `list(c(12, 3, 3))`, which I zero in on by running through the following steps: ``` as.list(body(pretty.default)) as.list(as.list(body(pretty.default))[[12]]) as.list(as.list(as.list(body(pretty.default))[[12]])[[3]]) as.list(as.list(as.list(body(pretty.default))[[12]])[[3]])[[3]] ``` I can then insert debugging code like this: ``` trace(what = 'pretty.default', tracer = quote(cat("\nThe value of delta is: ", delta, "\n\n")), at = list(c(12,3,3))) ## Try it a <- pretty(c(1, 7843)) b <- pretty(c(2, 23)) ## Clean up untrace('pretty.default') ``` Questions: So here are my questions: Is there a way to print out a function (or a parsed version of it) with the lines nicely labeled by the steps to which they belong? (According to Venables and Ripley, S-plus has a function `tprint()` that "produces a numbered listing of the body of a function for use with the `at` argument of `trace`", but R seems to have no equivalent.) Alternatively, is there another easier way, from the command line, to quickly set debugging code for a specific line within a function? Addendum: I used the `pretty.default()` example because it is reasonably tame, but with real/interesting functions, repeatedly using `as.list()` quickly gets tiresome and distracting. Here's an example: ``` as.list(as.list(as.list(as.list(as.list(as.list(as.list(as.list(as.list(body(# model.frame.default))[[26]])[[3]])[[2]])[[4]])[[3]])[[4]])[[4]])[[4]])[[3]] ```

Original source