Search code examples
rfunctionrecursiondependenciesfunction-declaration

Finding out which functions are called within a given function


Possible Duplicate:
Generating a Call Graph in R

I'd like to systematically analyze a given function to find out which other functions are called within that very function. If possible, recursively.

I came across this function in a blog post by milktrader with which I can do something similar for packages (or namespaces)

listFunctions <- function(
    name,
    ...
){ 
    name.0  <- name
    name    <- paste("package", ":", name, sep="")
    if (!name %in% search()) {
        stop(paste("Invalid namespace: '", name.0, "'"))
    }
    # KEEP AS REFERENCE       
#    out <- ls(name)
    funlist <- lsf.str(name)
    out     <- head(funlist, n=length(funlist))
    return(out)
}

> listFunctions("stats")
  [1] "acf"                  "acf2AR"               "add.scope"           
  [4] "add1"                 "addmargins"           "aggregate"           
  [7] "aggregate.data.frame" "aggregate.default"    "aggregate.ts"        
 [10] "AIC"                  "alias"                "anova"               
....
[499] "xtabs"   

Yet, I'd like a function where name would be the name of a function and the return value is a character vector (or a list, if done recursively) of functions that are called within name.

Motivation

I actually need some sort of character based output (vector or list). The reason for this is that I'm working on a generic wrapper function for parallelizing an abitrary "inner function" where you don't have to go through a time consuming trial-and-error process in order to find out which other functions the inner function depends on. So the output of the function I'm after will directly be used in snowfall::sfExport() and/or snowfall::sfSouce.

EDIT 2012-08-08

As there's been some close-votes due to duplicity, I'll check how answers can be merged with the other question tomorrow.


Solution

  • There must be better ways out there, but here's my attempt:

    listFunctions <- function(function.name, recursive = FALSE, 
                              checked.functions = NULL){
    
        # Get the function's code:
        function.code <- deparse(get(function.name))
    
        # break code up into sections preceding left brackets:
        left.brackets <- c(unlist(strsplit(function.code, 
                                           split="[[:space:]]*\\(")))
    
        called.functions <- unique(c(unlist(sapply(left.brackets, 
                                                   function (x) {
    
            # Split up according to anything that can't be in a function name.
            # split = not alphanumeric, not '_', and not '.'
            words <- c(unlist(strsplit(x, split="[^[:alnum:]_.]")))
    
            last.word <- tail(words, 1)
            last.word.is.function <- tryCatch(is.function(get(last.word)),
                                          error=function(e) return(FALSE))
            return(last.word[last.word.is.function])
        }))))
    
        if (recursive){
    
            # checked.functions: We need to keep track of which functions 
            # we've checked to avoid infinite loops.
            functs.to.check <- called.functions[!(called.functions %in%
                                              checked.functions)]
    
            called.functions <- unique(c(called.functions,
                do.call(c, lapply(functs.to.check, function(x) {
                    listFunctions(x, recursive = T,
                                  checked.functions = c(checked.functions,          
                                                        called.functions))
                    }))))
        }
        return(called.functions)
    }
    

    And the results:

    > listFunctions("listFunctions", recursive = FALSE)
     [1] "function"      "deparse"       "get"           "c"            
     [5] "unlist"        "strsplit"      "unique"        "sapply"       
     [9] "tail"          "tryCatch"      "is.function"   "return"       
    [13] "if"            "do.call"       "lapply"        "listFunctions"
    
    > system.time(all.functions <- listFunctions("listFunctions", recursive = TRUE))
       user  system elapsed 
      92.31    0.08   93.49 
    
    > length(all.functions)
      [1] 518
    

    As you can see, the recursive version returns a lot of functions. The problem with this is it returns every function called in the process, which obviously adds up as you go. In any case, I hope you can use this (or modify it) to suit your needs.