Como obter o número da linha de uma chamada de função em R?

8

Para fins de depuração, quero imprimir um número de linha (e nome da função) do local em que a função atual foi chamada. Como faço para obter isso em R?

Eu já vi uma solução para obter o nome do arquivo de origem. Mas como obter o número da linha e o nome da função?]

EDIT: descobri como obter esses dados de traceback()alguma forma, o traceback é capaz de imprimi-los, mas não sei como decodificar as informações:

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> 
TMS
fonte

Respostas:

4

Encontrei uma solução! Entendi do código de 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

Escreveu uma boa função de wrapper para ela:

# 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
}

É assim que é usado:

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

A única limitação (menor) é que, quando chamada em subexpressões como cat(caller_info("Called from %f at %s#%l\n"))ou cat(paste0(caller_info("Called from %f at %s#%l\n"))), R confunde essas coisas de subexpressão como níveis de pilha, o que estraga tudo. Portanto, é melhor evitar o uso desse wrapper em expressões.

TMS
fonte
Agradável! Eu não sabia sobre o argumento opcional para .traceback().
user2554330
1
A única alteração que eu faria é usar as funções do extrator e getSrcref, em vez de trabalhar diretamente nos atributos. O formato interno pode mudar. getSrcFilenamegetSrcLocation
user2554330
@ user2554330 obrigado, ótimo comentário! :-) Atualizei meu invólucro.
TMS
0

Não há funções fáceis para fornecer o que você está solicitando, mas, para fins de depuração, você pode chamar browser()uma função e executar o wherecomando para ver a pilha de chamadas atual. Por exemplo, você pode ver algo assim:

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)

Isso fornece locais para algumas chamadas, mas não para todas.

Se você realmente deseja algo que seja impresso à medida que avança (como as macros __LINE__e __FILE__em C / C ++), é um pouco mais difícil. Isso imprime o local atual:

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

Nem todas as funções têm nomes e as funções R não sabem com que nome você as chamou, mas é possível ver a chamada atual usando sys.call(). Então isso imprime tudo:

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

que pode imprimir

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

sys.call tem um argumento para subir a pilha, mas não sei como obter as informações do número da linha.

Você pode obter a localização do início da função que fez a chamada atual usando

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

que mostra o código que fez a chamada, mas o número da linha é apenas para a função da qual ela veio. É um bom argumento para manter suas funções curtas!

user2554330
fonte
hmm ... isso não é interessante ... mas é realmente importante para mim obter o número da linha e a função da chamada dessa função (de onde é chamada). O número da linha atual é claro e não é interessante.
TMS
Em seguida browser(), use , possivelmente acionado por um erro ou aviso, ou use cat("Called from ",getSrcFilename(sys.function(-1)), " line ",getSrcLocation(sys.function(-1), "line"), "\n"), que fornecerá o local da função, não o local real da chamada.
user2554330