'How to find a line corresponding to the step in the body?

Let's take the code as below saved in file "test.R":

fun1 <- function() {
c(1,
2,
3)
}

Let's say in the file it starts from line 1, i.e. fun1 definition is from line 1 to 5. I can now use utils::findLineNum("test.R", line = 3)[[1]]$at to get the location in the body, but how to get a line having the location in the body?

Full code:

fun1 <- function() {
  c(1,
    2,
    3)
}

at <- utils::findLineNum("test.R", line = 3)[[1]]$at

getSrcLocation(body(fun1)[[at]], "line") # returns NULL :(

Is there some specific function for this? The problem is that in the example above, the at points to the line 2, not 3 if I can say that (we can check this using utils::findLineNum("test.R", line = 2)[[1]]$at: returned value is the same as for line 3) and I need to get this first line to which the at refers to.

fun1 <- function() {
  c(1,
    2,
    3)
}

at <- utils::findLineNum("test.R", line = 3)[[1]]$at

body(fun1)[[at]] <- "In which line am I assuming function starts from line 1?"

body(fun1)

Above, at determines the part of the body which will be replaced by string and this string will be in line 2. How to get this information that it will be line 2?


At first I thought I could do something similar as @Allan Cameron said in the answer, I mean "see when 'at' has changed' (using identical(), but then I got this example:

fun1 <- function() {
    reactable::reactable(src_data,
                         columns = list(col = reactable::colDef(cell = function(value) {
                                                                       if (TRUE) {
                                                                         my_fun(value)
                                                                       } else {
                                                                         value
                                                                       }
                                                                     }
                                        )),
                         highlight = TRUE)
}

And the line of interest was the line with highlight = TRUE), so the line 11 (counting from 1) and my solution didn't work because of this nested if else (@Allan Cameron proposition do not work as well because of some error). Then I thought I could use sum() instead of identical() to check when the sum starts to be lower. But the problem is - is the code below reliable?:

fun1 <- function() {
    reactable::reactable(src_data,
                         columns = list(col = reactable::colDef(cell = function(value) {
                                                                       if (TRUE) {
                                                                         my_fun(value)
                                                                       } else {
                                                                         value
                                                                       }
                                                                     }
                                        )),
                         highlight = TRUE)
}

determine_line <- function(file, line, object_envir_orig, object_at_orig) {
  still_lines_to_check <- TRUE
  while (still_lines_to_check) {
    line <- line - 1
    obj <- utils::findLineNum(file, line, nameonly = FALSE, envir = object_envir_orig, lastenv = object_envir_orig)
    if (length(obj) > 0) {
      obj <- obj[[length(obj)]]
      at <- obj$at
      if (sum(at) < sum(object_at_orig)) {
        line <- line + 1
        still_lines_to_check <- FALSE
      }
    } else {
      still_lines_to_check <- FALSE
    }
  }
  line
}

determine_line("file.R", 11, environment(fun1), findLineNum("file.R", 11)[[1]]$at)

And also - I was hoping to have something faster than determine_line().



Solution 1:[1]

One way to do this is to backtrack through the lines until the at changes, which means you are no longer within the same step of the function. Keep track of the line as you do this. The following function gives an example of how this might work, along with forward tracking to tell you where the step ends. This can be simplified as needed. Note it does not require first sourcing the file, since it is done within the function.

locate_body_line <- function(file, line_num) {
  source(file, local = TRUE)
  line_info <- findLineNum(file, line = line_num)[[1]]
  at <- line_info$at[1]
  
  f <- get(line_info$name)
  
  starts <- at
  ends   <- at
  myline <- line_num
  
  while(starts == at) {
    myline <- myline - 1
    ln <- findLineNum(file, line = myline)[[1]]
    starts <-  ln$at
  }

  starts <- myline + 1
  myline <- line_num
  
  while(ends == at) {
    myline <- myline + 1
    ln <- findLineNum(file, line = myline)
    if(length(ln) == 0) break
    ends <-  ln[[1]]$at
  }
  ends <- myline - 1

  cat("Line ", line_num, " of file '", file, 
      "' is within the function \"", line_info$name, "\"\n", sep = "")
  cat("\nThe line includes the step:\n", deparse(body(f)[[at]]),
      "\nwhich starts on line", starts, "and ends on line", ends)
}
locate_body_line("../test.R", line = 3)
#> Line 3 of file '../test.R' is within the function "fun1"
#> 
#> The line includes the step:
#>  c(1, 2, 3) 
#> which starts on line 2 and ends on line 4

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Allan Cameron