I am trying to write a function in R called functionTree that takes an R expression and prints its structure in a tree format. The function should display the elements of the expression with their types and indent them according to their depth in the tree. I have the following requirements:
The assignment operator (<-) should be displayed at depth 0, as function: special. The left-hand side of the assignment should be displayed at depth 0. The function call on the right-hand side should be displayed at depth 1, f should be displayed as function:closure. The arguments of the function call should be displayed at depth 1, as atoms.
I have the following code, but it doesn't produce the expected indentation and depth levels:
functionTree <- function(expr, eframe = globalenv(), maxDepth = 3, closureRecursive = FALSE, depth = 0) {
# Helper function to determine the type description
getTypeDescription <- function(expr) {
if (is.symbol(expr)) {
return("symbol")
} else if (is.call(expr)) {
funcName <- as.character(expr[[1]])
if (funcName %in% c("<-", "=")) {
return("function:special")
} else if (funcName == "function") {
return("function:closure")
} else {
return("function:closure")
}
} else if (is.atomic(expr) && length(expr) == 1) {
return("atom")
} else {
return("unknown")
}
}
# Function to print the current expression and its type
printExpression <- function(expr, depth) {
indent <- paste(rep(" ", depth * 2), collapse = "") # Create indentation based on depth
typeDescription <- getTypeDescription(expr)
exprDescription <- deparse(expr)
if (length(exprDescription) > 1) {
exprDescription <- exprDescription[1]
}
# Ensure there is no attempt to print zero-length variable name
if (nchar(exprDescription) == 0) {
exprDescription <- "<empty>"
}
cat(indent, depth, " (", typeDescription, ") -> ", exprDescription, "\n", sep = "")
}
# Base case: Stop the recursion if the maximum depth is exceeded
if (depth > maxDepth) {
return(invisible())
}
# Print the current expression
printExpression(expr, depth)
# If the expression is a call, recursively analyze its arguments
if (is.call(expr)) {
funcName <- as.character(expr[[1]])
if (funcName %in% c("<-", "=")) {
# Handle special case for assignment
functionTree(expr[[1]], eframe, maxDepth, closureRecursive, depth) # Print the assignment operator
functionTree(expr[[2]], eframe, maxDepth, closureRecursive, depth) # Print the left-hand side
functionTree(expr[[3]], eframe, maxDepth, closureRecursive, depth + 1) # Print the right-hand side
} else {
functionTree(expr[[1]], eframe, maxDepth, closureRecursive, depth + 1)
lapply(expr[-1], function(sub_expr) functionTree(sub_expr, eframe, maxDepth, closureRecursive, depth + 1))
}
}
}
# Example usage of the functionTree function
f <- function(n = 0) { n ^ 2 }
a <- quote(x <- f(5))
# Call the functionTree function with the example expression
functionTree(a)
I tried adjusting the depth and indentation within the function, but I couldn't achieve the desired output. Any help to correct the function to produce the expected output would be appreciated.
My output:
0 (function:special) -> x <- f(5)
0 (symbol) -> <-
0 (symbol) -> x
1 (function:closure) -> f(5)
2 (symbol) -> f
2 (atom) -> 5
[[1]]
NULL
Expected output:
0 (function:special) -> <-
0 (symbol) -> x
1 (function:closure) -> f
1 (atom) -> 5
This version seems to get you closer
functionTree <- function(expr, eframe = globalenv(), maxDepth = 3, closureRecursive = FALSE, depth = 0) {
# Helper function to determine the type description
getTypeDescription <- function(expr) {
if (is.symbol(expr)) {
return("symbol")
} else if (is.call(expr)) {
funcName <- as.character(expr[[1]])
if (funcName %in% c("<-", "=")) {
return("function:special")
} else if (funcName == "function") {
return("function:closure")
} else {
return("function:closure")
}
} else if (is.atomic(expr) && length(expr) == 1) {
return("atom")
} else {
return("unknown")
}
}
# Function to print the current expression and its type
printExpression <- function(expr, depth) {
indent <- paste(rep(" ", depth * 2), collapse = "") # Create indentation based on depth
typeDescription <- getTypeDescription(expr)
exprDescription <- deparse(expr)
if (length(exprDescription) > 1) {
exprDescription <- exprDescription[1]
}
# Ensure there is no attempt to print zero-length variable name
if (nchar(exprDescription) == 0) {
exprDescription <- "<empty>"
}
cat(indent, depth, " (", typeDescription, ") -> ", exprDescription, "\n", sep = "")
}
# Base case: Stop the recursion if the maximum depth is exceeded
if (depth > maxDepth) {
return(invisible())
}
# If the expression is a call, recursively analyze its arguments
if (is.call(expr)) {
funcName <- as.character(expr[[1]])
if (funcName %in% c("<-", "=")) {
# Handle special case for assignment
functionTree(expr[[1]], eframe, maxDepth, closureRecursive, depth) # Print the assignment operator
functionTree(expr[[2]], eframe, maxDepth, closureRecursive, depth + 1) # Print the left-hand side
functionTree(expr[[3]], eframe, maxDepth, closureRecursive, depth + 1) # Print the right-hand side
invisible(NULL)
} else {
functionTree(expr[[1]], eframe, maxDepth, closureRecursive, depth)
lapply(expr[-1], function(sub_expr) functionTree(sub_expr, eframe, maxDepth, closureRecursive, depth + 1))
invisible(NULL)
}
} else {
printExpression(expr, depth)
}
}
The primary thing was to move the printExpression
call to the else condition when checking for calls. Otherwise you were just processing the same expression too many times. It also seems like the depth should match the indenting level from the code so this outputs
0 (symbol) -> <-
1 (symbol) -> x
1 (symbol) -> f
2 (atom) -> 5
Otherwise I'm not sure why you have "0" in "0 (symbol) -> x" in your expected output.