I have a function such as this one :
fun <- function() {
browser()
is_browsing()
}
I would like to know what the code of is_browsin
Starting with the ideas in Romain's code, then copying across the RCNTXT struct (plus a couple of other structs it uses internally), I managed to get the C++ code to return the contents of R_GlobalContext
.
The C++ code looks like this:
#include
#include
#include
extern void* R_GlobalContext ;
typedef struct {int tag, flags; union {int ival; double dval; SEXP sxpval;} u;
} R_bcstack_t;
typedef struct{jmp_buf jmpbuf; int mask_was_saved, saved_mask;} sigjmp_buf[1];
typedef struct RCNTXT {
struct RCNTXT *nextcontext;
int callflag;
sigjmp_buf cjmpbuf;
int cstacktop, evaldepth;
SEXP promargs, callfun, sysparent, call, cloenv, conexit;
void (*cend)(void *);
void *cenddata;
void *vmax;
int intsusp, gcenabled, bcintactive;
SEXP bcbody;
void* bcpc;
SEXP handlerstack, restartstack;
struct RPRSTACK *prstack;
R_bcstack_t *nodestack;
R_bcstack_t *bcprottop;
SEXP srcref;
int browserfinish;
SEXP returnValue;
struct RCNTXT *jumptarget;
int jumpmask;
} RCNTXT, *context;
// [[Rcpp::export]]
Rcpp::List get_RCNTXT(int level){
RCNTXT* res = (RCNTXT*)R_GlobalContext;
if (level > 1) res = res->nextcontext;
return Rcpp::List::create(Rcpp::Named("call_flag") = res->callflag,
Rcpp::Named("c_stack_top") = res->cstacktop,
Rcpp::Named("call_depth") = res->evaldepth,
Rcpp::Named("call_fun") = res->callfun,
Rcpp::Named("sys_parent") = res->sysparent,
Rcpp::Named("call") = res->call,
Rcpp::Named("cloenv") = res->cloenv,
Rcpp::Named("conexit") = res->conexit,
Rcpp::Named("promargs") = res->promargs,
Rcpp::Named("intsusp") = res->intsusp,
Rcpp::Named("gcenabled") = res->gcenabled,
Rcpp::Named("bcintactive") = res->bcintactive,
Rcpp::Named("handlerstack") = res->handlerstack,
Rcpp::Named("restartstack") = res->restartstack,
Rcpp::Named("srcref") = res->srcref,
Rcpp::Named("browserfinish") = res->browserfinish);
}
That allows us to review the contents of R_Globalcontext
:
get_RCNTXT(1)
#> $call_flag
#> [1] 12
#>
#> $c_stack_top
#> [1] 4
#>
#> $call_depth
#> [1] 1
#>
#> $call_fun
#> function (level)
#> .Call(, level)
#>
#>
#> $sys_parent
#>
#>
#> $call
#> get_RCNTXT(1)
#>
#> $cloenv
#>
#>
#> $conexit
#> NULL
#>
#> $promargs
#> $promargs[[1]]
#> NULL
#>
#>
#> $intsusp
#> [1] 0
#>
#> $gcenabled
#> [1] 1
#>
#> $bcintactive
#> [1] 0
#>
#> $handlerstack
#> NULL
#>
#> $restartstack
#> NULL
#>
#> $srcref
#> NULL
#>
#> $browserfinish
#> [1] 0
Unfortunately, the browserfinish
field just returns a 0 whether called from browser
or not. However, if the get_RCNTXT
function is called from the browser
prompt, the restartstack
shows that it has been called from browser
. This allows the following R function to be defined once the C++ code has been sourced:
is_browser <- function()
{
R <- get_RCNTXT(1)$restartstack
if(is.null(R)) return(FALSE)
class(R[[1]]) == "restart"
}
This allows the browser state to be queried from the command prompt:
is_browser()
#> [1] FALSE
> browser()
#> Called from: top level
Browse[1]> is_browser()
#> [1] TRUE
However, this is not as useful as it seems. Firstly, it has the same effect as the following code in base R:
is_browser <- function() {
!is.null(findRestart("browser"))
}
Secondly, when browser
is called from inside a function, the code it runs is evaluated in its own context rather than the browser
context, meaning is_browser
will return FALSE. The C code for browser
, (the actual function is called do_browser
in main.c) writes a new context which is removed after the function exits, and this context is apparently not pointed at by any other structure for the duration of the function, so it is difficult to see how is_browser
could be written to allow access to this context.
It therefore seems you would need to write a new implementation of browser
to allow the browsed context to know that it was being browsed, and we really don't want to go there.
On the other hand, the browser context has full access to the browsed context, and since your end goal is to allow conditional code like plots to run only when in browser mode, I think the best solution is to use the browser itself to tell the browsed context that it is being browsed.
So for example, if you do:
browser_on <- function() {
options(I_am_browsing = TRUE)
}
browser_off <- function() {
options(I_am_browsing = FALSE)
}
is_browser <- function() {
b <- getOption("I_am_browsing")
if(is.null(b)) FALSE else b
}
You now have the option while browsing to conditionally run code that is protected by if(is_browser())
.
Then if you have fun
like this (with browser()
commented out):
fun <- function() {
#browser()
if(is_browser()) plot(1:10)
if(!is_browser()) "I didn't plot anything"
}
You will get:
fun()
#> [1] "I didn't plot anything"
But, if you run fun()
from inside a browser, you get:
browser()
Called from: top level
Browse[1]> browser_on()
Browse[1]> fun()
And it still works if browser
is called inside fun
:
fun <- function() {
browser()
if(is_browser()) plot(1:10)
if(!is_browser()) "I didn't plot anything"
}
fun()
#> Called from: fun()
Browse[1]> browser_on()
Browse[1]>
#> debug at #3: if (is_browser()) plot(1:10)
Browse[2]>
#> debug at #3: plot(1:10)
Browse[2]>
#> debug at #4: if (!is_browser()) "I didn't plot anything"
Browse[2]>
It's not a perfect solution because it requires an extra command while running in the browser, and it saves state via options
. You will need to keep track of this if you call browser
multiple times from the same scope. In particular, you shoud be careful to call browser_off()
before exiting the browser if you are calling browser
from the global environment.