Skip to content

Instantly share code, notes, and snippets.

@moodymudskipper
Last active November 6, 2020 04:42
Show Gist options
  • Save moodymudskipper/23ba69e2dd60639ae843aa71ab1c93f4 to your computer and use it in GitHub Desktop.
Save moodymudskipper/23ba69e2dd60639ae843aa71ab1c93f4 to your computer and use it in GitHub Desktop.
find classes
# run from R GUI or you might have methods registered by IDE
# first method scrape S3 method tables, classes with no methods are not found
# second method parses code to find `class(foo) <- bar` lines and extracts string litterals if found, we could be a bit smarter there
# and find more, but that will still not be exhaustive, because we have things like `class(x) <- cl` and we'd have to check the code
# to see what `cl` is.
# we could also check if some objects are built with `structure`
# calls to `inherits` might also be checked
# The C code should also be inspected or we won't find for instance the "error" or "try-error" classes.
find_classes <- function(pkgs = c("base", "methods", "utils", "grDevices", "graphics", "stats")) {
res <- lapply(pkgs, function(pkg) {
env <- as.environment(asNamespace(pkg))
methods <- ls(getFromNamespace(".__S3MethodsTable__.", pkg))
split_ <- strsplit(methods, "\\.")
res <- lapply(split_, function(x) {
L <- length(x)
if(L == 2) {
fun_nms <- x[[2]]
} else {
fun_nms <- list()
for(i in seq(L-1)) {
fun_nm <- paste(x[1:i], collapse= ".")
fun <- get0(fun_nm,mode = "function", envir = env)
if(!is.null(fun)) {
if(!is.null(body(fun)) && isS3stdGeneric(fun))
fun_nms <- append(fun_nms, paste(x[-(1:i)], collapse= "."))
}
}
}
fun_nms
})
sort(unique(unlist(res)))
})
sort(unique(unlist(res)))
}
find_classes()
#~~~~~~checking all `class(foo) <- bar` calls for strng litteral rhs (or `c` of thereof)
#' Recurse Through a Call to Extract or Replace
#'
#' @param call a call
#' @param find, a language object, a litteral, or a function with `call`
#' and `ind` arguments returning a boolean.
#' @param replace a language object, a litteral or a function with `call`
#' and `ind` arguments returning a language object.
#' @param output either
#' `"call"` (default) to replace in the call,
#' `"list"` to extract the matches (replaced if `replace` isn't `NULL`), or
#' `"indices"` to extract the indices of the match into a list (`replace` will
#' be ignored)
#' @examples
#' call <- quote(f(apple(1,2,3), orange(a, b, c), f(orange(d, e))))
#' call
#'
#' # find indices or locations of `orange` symbol
#' call_apply(call, quote(orange), output = "i")
#'
#' # replace those with `pear`
#' call_apply(call, quote(orange), quote(pear))
#'
#' # replace 1 with 100
#' call_apply(call, 1, 100)
#'
#' # replace calls to orange with a `pear` symbol
#' find_orange_call <- function(call, ind)
#' is.call(call[[ind]]) && identical(call[[c(ind,1)]], quote(orange))
#' call_apply(call, find_orange_call, quote(pear))
#'
#' # replace `orange` with a `pear` only if found at depth 3
#' find_orange_sym_d3 <- function(call, ind)
#' identical(call[[ind]], quote(orange)) && length(ind) == 3
#' call_apply(call, find_orange_sym_d3, quote(pear))
#'
#' # replace depth 2 syms with upper case
#' find_d2_sym <- function(call, ind) is.symbol(call[[ind]]) && length(ind) == 2
#' sym_toupper <- function(call, ind) as.symbol(toupper(as.character(call[[ind]])))
#' call_apply(call, find_d2_sym, sym_toupper)
#'
#' # extract the latter
#' call_apply(call, find_d2_sym, sym_toupper, out = "l")
call_apply <- function(call, find, replace = NULL, output = c("call", "list", "indices")) {
output = match.arg(output)
fun_bool <- is.function(call)
if(fun_bool) {
call_bkp <- call
call <- body(call)
}
if (is.symbol(call)) {
call <- call("{", call)
}
#~~~~~~~~~~~~~~~~~~~~
# find
if(!is.function(find))
find <- as.function(c(
alist(call=, ind=), bquote(identical(call[[ind]], quote(.(find))))))
if(!is.null(replace) && !is.function(replace))
replace <- as.function(c(
alist(call=, ind=), bquote(quote(.(replace)))))
fetch_indices <- function(ind) {
# return ind if target was found
if(find(call, ind = ind)) return(ind)
# if call is not a call we're on a leaf, nothing else to do
if(!is.call(call[[ind]])) return(NULL)
# go through items and recurse with updated ind
lapply(seq_along(call[[ind]]), function(i) fetch_indices(c(ind, i)))
}
# get sparse nested list
indices <- lapply(seq_along(call), fetch_indices)
# use rapply to flatten it, as.call necessary not to flatten vectors
indices <- rapply(indices, function(x) as.call(c(quote(c), x)), how = "unlist")
# eval items
indices <- lapply(indices, eval)
if(output == "indices") return(indices)
#~~~~~~~~~~~~~~~~~~~~
# replace
if(output == "call") {
res <- call
for(ind in indices) {
res[[ind]] <- replace(call, ind)
}
if(fun_bool) {
body(call_bkp) <- res
return(call_bkp)
}
return(res)
}
#~~~~~~~~~~~~~~~~~~~~~~
# extract
if(is.null(replace))
replace <- as.function(c(
alist(call=, ind=), quote(call[[ind]])))
lapply(indices, function(ind) replace(call, ind))
}
find_class_assign_call <- function(call, ind) {
is.call(call[[ind]]) &&
identical(call[[c(ind,1)]], quote(`<-`)) &&
is.call(call[[c(ind,2)]]) &&
identical(call[[c(ind,2,1)]], quote(`class`))}
find_classes <- function(pkgs = c("base", "methods", "utils", "grDevices", "graphics", "stats")) {
calls <- lapply(pkgs, function(pkg) {
all_funs <- ls(asNamespace(pkg))
lapply(all_funs, function(fun_nm) {
#print(fun_nm)
fun <- getFromNamespace(fun_nm, ns = pkg)
if(is.function(fun))
call_apply(fun, find_class_assign_call, output = "l")
else
NULL
})
})
calls <- Filter(length, unlist(calls))
calls <- lapply(calls, function(x) {
if(is.character(x[[3]])) x[[3]] else {
if(is.call(x[[3]]) && identical(x[[c(3,1)]], quote(`c`))) {
Filter(is.character, as.list(x[[3]]))
}
}
})
calls <- Filter(length, unlist(calls))
sort(unique(calls))
}
find_classes()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment