Home Comments Thread
New Thread

Finding Function Calls in R Code | /en/2023/01/func-call/

giscus giscus 2023-01-22 17:53:59

Finding Function Calls in R Code

Yesterday I learned an unexpected but interesting use of the highr package from a Github issue. This package is intended for syntax highlighting R code, but the user wanted to identify function calls …

https://yihui.org/en/2023/01/func-call/

2 Comments

JeffreyRStevens JeffreyRStevens 2023-01-22 17:54:00

This is super helpful--thanks! Do you know of anything similar for extracting functions from R Markdown/Quarto files (both in the text and code chunks)? For example, if I wanted to find all of the functions referenced in a chapter of R4DS.

yihui yihui 2023-01-23 04:39:57

Here is a function to extract all R code from an Rmd file:

extract_code = function(file) {
  x = xfun::read_utf8(file)
  on.exit(knitr::knit_code$restore(), add = TRUE)
  res = knitr:::split_file(x, patterns = knitr::all_patterns$md)
  unlist(lapply(res, function(el) {
    if (is.null(label <- el$params$label)) el$code else {
      if (is.null(lang <- el$params$engine) || tolower(lang) == 'r')
        knitr::knit_code$get(label)
    }
  }))
}

With this function and find_funs() in the above post, you can find functions via

find_funs(extract_code('file.Rmd'))
JeffreyRStevens JeffreyRStevens 2023-01-23 22:41:59

Excellent--thank you! I'll give it a shot.

JeffreyRStevens JeffreyRStevens 2024-09-26 14:51:15

Any chance you have an exported version of the extract_code() function published in a package somewhere? Or is it possible to export knitr:::split_file()? Since it's not exported, I get the R CMD check note about unexported objects. Or is there another way to get this functionality into a package I'm working with?
Many thanks for your efforts on this!

yihui yihui 2024-09-26 15:23:23

I have rewritten knitr and a large part of the R Markdown ecosystem from scratch this year as the new package litedown. You can use the exported function litedown::crack() to parse an Rmd file, which is much more robust than knitr's split_file(). Since this package is brand new, I'd love to hear your thoughts and improve it.

JeffreyRStevens JeffreyRStevens 2024-09-26 15:25:01

That is amazing! Many thanks! I'll go check it out.

JeffreyRStevens JeffreyRStevens 2024-09-27 13:36:18

Thanks for your help. Everything seems to have worked fine. I had to tweak the functions a little bit. Here's what I ended up with:

extract_code <- function(file) {
  x <- xfun::read_utf8(file)
  res <- litedown::crack(x)
  unlist(lapply(res, function(el) {
    if (el$type == "code_chunk") el$source
  }))
}

extract_functions <- function(code) {
  d <- getParseData(x = parse(text = code, keep.source = TRUE))
  f <- d[d$token == 'SYMBOL_FUNCTION_CALL', 'text']
  for (s in d[d$token == 'SYMBOL', 'text']) {
    tryCatch({
      ev <- eval(as.symbol(s), parent.frame())
      if (is.function(ev)) f = c(f, s)
    }, error = function(e) NULL)
  }
  f
}

Are you OK with me adding these functions to my flashr package under an MIT License (with a link to this page in the documentation)?

yihui yihui 2024-09-27 14:42:23

You need to check el$options$engine == 'r' in addition to el$type == 'code_chunk', in case there are code chunks of other languages. You don't need xfun::read_utf8(), but can just pass the path to crack(), i.e., litedown::crack(file).

I'm okay with whatever license you prefer. It's just a short piece of code.

BTW, I just took a quick look at flashr, and I really loved the idea! It looks very interesting and helpful! I have had a lot of pain myself in teaching my kids Chinese characters, and have also been thinking of trying flash cards. I've written a simple app a few years ago, which was too boring to them and also very inefficient: https://yihui.org/cn/kids/2021/02/chars/

Sign in to join the discussion

Sign in with GitHub