Skip to content

Instantly share code, notes, and snippets.

@lionel-
Last active February 24, 2022 16:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lionel-/1142569ba7844ac2f24cf0fa27cdabf9 to your computer and use it in GitHub Desktop.
Save lionel-/1142569ba7844ac2f24cf0fa27cdabf9 to your computer and use it in GitHub Desktop.
Unwinding scope in `source()`
# Prototype of `source()` as currently implemented
source2 <- function(file, env = parent.frame()) {
exprs <- parse(file)
for (expr in exprs) {
eval(expr, env)
}
}
# Prototype of `source()` using an evaluation strategy where the
# unwinding scope is the whole file rather than each line
source3 <- function(file, env = parent.frame()) {
exprs <- parse(file)
cb <- function() {
for (expr in exprs) {
# Using `delayedAssign()` as an interface to the C-level function
# `Rf_eval()`. Unlike the R-level `eval()`, this doesn't create
# an unwinding scope.
eval(bquote(delayedAssign("do", .(expr), eval.env = env)))
do
}
}
# Here we use `eval()` to create an unwinding scope for `env`.
# We call ourselves back immediately once the scope is created.
eval(as.call(list(cb)), env)
invisible()
}
# Let's try these functions on the following script
file <- tempfile()
cat(file = file, "
on.exit(cat('on exit!\\n'))
cat('now!\\n')
return()
cat('never run!\\n')
")
# Whereas `source()` and `source2()` have linewise unwinding scope,
# `source3()` has filewise unwinding scope
source(file)
#> on exit!
#> now!
#> never run!
source2(file)
#> on exit!
#> now!
#> never run!
source3(file)
#> now!
#> on exit!
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment