-
Notifications
You must be signed in to change notification settings - Fork 93
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support unscoped functions and library functions #452
Conversation
I am not sure if I like how
Insider the hook functions, we could modify the parse environment, redispatch For specific usage, we also allow users to override them individually. |
The ideal way is definitely a nice list of hook functions as you described. The assignment expressions, however, are somewhat special as they are only slightly different but do a lot of things in common. Let me see if I could make it look better. |
If we make all functions equivalent in Another approach is to make the hook functions more descriptive by returning a structure that described what should be done: assignment, recall, or they could modify the input |
I guess it makes sense to expose to uses only a limited set of actions via some helper functions. Either we could pass those functions in a list via the hook functions or define a R6 class that contains all the relevant methods. |
I come up with the following functions defined by default in parser_hooks <- list(
"{" = function(expr, env) {
list(recall = as.list(expr)[-1L])
},
"(" = function(expr, env) {
list(recall = as.list(expr)[-1L])
},
"if" = function(expr, env) {
list(recall = as.list(expr)[2:4])
},
"for" = function(expr, env) {
if (is.symbol(e <- expr[[2L]])) {
env$nonfuncts <- c(env$nonfuncts, as.character(e))
}
list(recall = expr[[4L]])
},
"while" = function(expr, env) {
list(recall = as.list(expr)[2:3])
},
"repeat" = function(expr, env) {
list(recall = expr[[2L]])
},
"<-" = function(expr, env) {
if (length(expr) == 3L && is.symbol(expr[[2L]])) {
list(
assign = list(symbol = as.character(expr[[2L]]), value = expr[[3L]]),
recall = expr[[3L]]
)
}
},
"=" = function(expr, env) {
if (length(expr) == 3L && is.symbol(expr[[2L]])) {
list(
assign = list(symbol = as.character(expr[[2L]]), value = expr[[3L]]),
recall = expr[[3L]]
)
}
},
"assign" = function(expr, env) {
call <- match.call(base::assign, as.call(expr))
if (is.character(call$x) && is_top_level(call$pos, -1L, -1) && is_top_level(call$envir)) {
list(
assign = list(symbol = call$x, value = call$value),
recall = call$value
)
}
},
"delayedAssign" = function(expr, env) {
call <- match.call(base::delayedAssign, as.call(expr))
if (is.character(call$x) && is_top_level(call$assign.env)) {
list(
assign = list(symbol = call$x, value = call$value),
recall = call$value
)
}
},
"makeActiveBinding" = function(expr, env) {
call <- match.call(base::makeActiveBinding, as.call(e))
if (is.character(call$sym) && is_top_level(call$env)) {
list(
assign = list(symbol = call$sym, value = call$fun, type = "variable")
)
}
},
"library" = function(expr, env) {
call <- match.call(base::library, expr)
if (!isTRUE(call$character.only)) {
env$packages <- union(env$packages, as.character(call$package))
}
NULL
},
"require" = function(expr, env) {
call <- match.call(base::require, call)
if (!isTRUE(call$character.only)) {
env$packages <- union(env$packages, as.character(call$package))
}
NULL
},
"pacman::p_load" = function(expr, env) {
fun <- if (requireNamespace("pacman")) pacman::p_load else
function(..., char, install = TRUE,
update = getOption("pac_update"),
character.only = FALSE) NULL
call <- match.call(fun, expr, expand.dots = FALSE)
if (!isTRUE(call$character.only)) {
env$packages <- union(
env$packages,
vapply(call[["..."]], as.character, character(1L))
)
}
NULL
},
"system.time" = function(expr, env) list(recall = "expr"),
"try" = function(expr, env) list(recall = "expr"),
"tryCatch" = function(expr, env) list(recall = c("expr", "finally")),
"withCallingHandlers" = function(expr, env) list(recall = "expr"),
"withRestarts" = function(expr, env) list(recall = "expr"),
"allowInterrupts" = function(expr, env) list(recall = "expr"),
"suspendInterrupts" = function(expr, env) list(recall = "expr"),
"suppressPackageStartupMessages" = function(expr, env) list(recall = "expr"),
"suppressMessages" = function(expr, env) list(recall = "expr"),
"suppressWarnings" = function(expr, env) list(recall = "expr")
) Then we process expressions recursively using these hook functions and act according to the return value as a set of pre-defined behavior (e.g. In this way, user could easily define new hooks or disable or overwriting existing one by setting it to Does this make sense? |
Thank you for the fast iteration. I like it better, but the return object still feels like a bit hard to understand and hard to extend, how about instead of returning a list, we pass an object
|
Actually, even better, we could have |
The "for" = function(expr, env) {
if (is.symbol(e <- expr[[2L]])) {
env$nonfuncts <- c(env$nonfuncts, as.character(e))
}
list(recall = expr[[4L]])
} How should we include this using |
maybe like this |
Awesome. I like how it looks like now. Good stuff! |
You might want to update the description for future users :). |
Thanks for the great suggestions! Descriptions are updated. |
hello, i'm trying to add a custom parser hook to be able to use options(languageserver.parser_hooks = list("import::from" = function(expr, env) {
call <- match.call(import::from, expr)
env$packages <- union(env$packages, as.character(call$.from))
})) do you know a way to show the log? |
Closes #257
Closes #426
Closes #451
This PR introduces customizable
unscopedparser hook functionsand library functionsso that the following code could be supported:The unscoped functions (e.g.try
,suppressPackageStartupMessages
,system.time
) evaluate an expression in its calling environment as if theexpr
is directly evaluated but with some other things done.The library functions (e.g.library
,require
) are a list of functions recognized as equivalent withlibrary
. An example ispacman::p_load
.User could add more functions via the optionlanguageserver.unscoped_functions
andlanguageserver.library_functions
respectively in.Rprofile
.The
parse_expr
function is rewritten with unified parser hooks defined in the package and could be modified by user vialanguageserver.parser_hooks
option.A parser hook is a function of
expr
(input expression currently processing), and anaction
(a list of action functions). For example, the for loop is handled by the following parser hook:In this function, it adds the iteration variable (
expr[[2L]]
) tononfuncts
as a non-function global variable, and continue to parse the for loop body (expr[[4L]]
).The pre-defined action functions include
update(...)
: update the character vector offuncts
,nonfuncts
,packages
, etc. For example,for (i in 1:10) { }
addsi
tononfuncts
.assign(symbol, value, type)
: create an assignment which adds new symbol definition and associated comment lines as its documentation.parse(expr)
: continue to parse the given expression.parse_args(args)
: continue to parse the expressions of the given matched function arguments. For example, some functions (e.g.system.time
,suppressPackageStartupMessages
) or arguments (e.g.tryCatch(expr=, finally=)
) evaluate expressions in the current scope so that we would like to capture these arguments of the function to continue to parse the expressions as being evaluated in the global scope.Implementation
Test cases