get browsing state in a function

前端 未结 4 1591
灰色年华
灰色年华 2021-02-13 20:30

I have a function such as this one :

fun <- function() {
  browser()
  is_browsing()
} 

I would like to know what the code of is_browsin

4条回答
  •  灰色年华
    2021-02-13 21:14

    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.

提交回复
热议问题