8

For debug purposes, I want to print a line number (and function name) of the place the current function was called from. How do I get this in R?

I've seen a solution of getting the source file name But how to get the line number and function name?]

EDIT: I found how to get this data from traceback() in some form, traceback is able to print it out, but I am not sure how to decode the information out of it:

f <- function () {
    traceback(x = 3, max.lines = 1)
}

g <- function()
{
    f()
}

x <- g()

source("file.R") # file with this code
# 5: g() at file.R#20
# 4: eval(ei, envir)
# 3: eval(ei, envir)
# 2: withVisible(eval(ei, envir))
# 1: source("file.R")

str(x[[1]])
# chr "g()"
# - attr(*, "srcref")= 'srcref' int [1:8] 20 1 20 8 1 8 20 20
#  ..- attr(*, "srcfile")=Classes 'srcfilecopy', 'srcfile' <environment:  0x0000000013a31700> 
Tomas
  • 57,621
  • 49
  • 238
  • 373

2 Answers2

7

Found a solution! Got it from the code of traceback():

f <- function ()
{
    x <- .traceback(x = 1)

    srcloc <- if (!is.null(srcref <- attr(x[[1]], "srcref"))) {
        srcfile <- attr(srcref, "srcfile")
        paste0("Called from ", x[[2]], ", at ", basename(srcfile$filename), "#", srcref[1])
    }

    cat(srcloc, "\n")
}

g <- function()
{
    f()
}

g()
# Called from g(), at file.R#15

Wrote a nice wrapper function for it:

# returns a list, unless fmtstring is specified
# level: 1 - caller of the caller of this function; 2 - its parent, 3 - its grand-parent etc.
# fmtstring: return format string: %f (function), %s (source file), %l (line)
# 
# example: str <- caller_info("Called from %f at %s#%l\n")
# !!! it won't work with e.g. cat(caller_info("Called from %f at %s#%l\n"))
# or cat(paste0(caller_info("Called from %f at %s#%l\n"))) !!!
caller_info <- function (fmtstring = NULL, level = 1) # https://stackoverflow.com/q/59537482/684229
{
    x <- .traceback(x = level + 1)

    i <- 1
    repeat { # loop for subexpressions case; find the first one with source reference
        srcref <- getSrcref(x[[i]])
        if (is.null(srcref)) {
            if (i < length(x)) {
                i <- i + 1
                next;
            } else {
                warning("caller_info(): not found\n")
                return (NULL)
            }
        }
        srcloc <- list(fun = getSrcref(x[[i+1]]), file = getSrcFilename(x[[i]]), line = getSrcLocation(x[[i]]))
        break;
    }

    if (is.null(fmtstring))
        return (srcloc)

    fmtstring <- sub("%f", paste0(srcloc$fun, collapse = ""), fmtstring)
    fmtstring <- sub("%s", srcloc$file, fmtstring)
    fmtstring <- sub("%l", srcloc$line, fmtstring)
    fmtstring
}

This is how it's used:

f <- function ()
{
    str <- caller_info("Called from %f at %s#%l\n")
    cat(str)
}

The only (minor) limitation is that when called in subexpressions like cat(caller_info("Called from %f at %s#%l\n")) or cat(paste0(caller_info("Called from %f at %s#%l\n"))), R confusingly counts these subexpression things as stack levels, which messes it up. So better avoid the use of this wrapper in expressions.

Tomas
  • 57,621
  • 49
  • 238
  • 373
  • Nice! I didn't know about the optional argument to `.traceback()`. – user2554330 Dec 31 '19 at 13:45
  • 1
    The only change I'd make is to use the extractor functions `getSrcref`, `getSrcFilename` and `getSrcLocation` instead of working directly on the attributes. The internal format might change. – user2554330 Dec 31 '19 at 13:49
  • @user2554330 thanks, great comment! :-) I've updated my wrapper. – Tomas Dec 31 '19 at 14:50
  • I don't like the wrapper, but I most definitely like the core (base / top) function. This function helped me find a solution to the _``R Editor Line Number``_ problem I have been struggling with for more than a year !! – SilSur Jul 14 '22 at 15:26
0

There aren't easy functions to give you what you're asking for, but for debugging purposes you can call browser() in a function, then run the where command to see the current call stack. For example, you might see something like this:

where 1: calls()
where 2 at ~/temp/test.R#6: print(calls())
where 3 at ~/temp/test.R#9: f()
where 4: eval(ei, envir)
where 5: eval(ei, envir)
where 6: withVisible(eval(ei, envir))
where 7: source("~/temp/test.R", echo = TRUE)

This gives locations for a couple of the calls, but not all of them.

If you really want something that prints as you go (like the __LINE__ and __FILE__ macros in C/C++), it's a little harder. This prints the current location:

cat("This is line ", getSrcLocation(function() {}, "line"),
  " of ", getSrcFilename(function() {}))

Not all functions have names, and R functions don't know what name you called them under, but you can see the current call using sys.call(). So this prints everything:

  cat("This is line ", getSrcLocation(function() {}, "line"),
      " of ", getSrcFilename(function() {}), 
      " called as", deparse(sys.call()), 
      "\n")

which might print

This is line  3  of  test.R  called as f() 

sys.call has an argument to move up the stack, but I don't know of a way to get the line number information.

You can get the location of the start of the function that made the current call using

cat("Called from ", getSrcFilename(sys.function(-1)), " line ", getSrcLocation(sys.function(-1), "line"), 
    " as ", deparse(sys.call()), "\n")

which will show you the code that made the call, but the line number is only for the function it came from. It's a good argument for keeping your functions short!

user2554330
  • 37,248
  • 4
  • 43
  • 90
  • hmm... this is isnteresting... but it's really important for me to get the line number and function of the *call* of this function (where it's called from). The current line number is clear and not interesting. – Tomas Dec 31 '19 at 01:41
  • Then use `browser()`, possibly triggered by an error or warning, or use `cat("Called from ",getSrcFilename(sys.function(-1)), " line ",getSrcLocation(sys.function(-1), "line"), "\n")`, which will give you the function location, not the actual call location. – user2554330 Dec 31 '19 at 10:42