demoSource <- function(demo = localRFiles(ask=TRUE), inputCon = .demoFifo(), where = .GlobalEnv) { if(is.null(inputCon) || !isOpen(inputCon)) { message("The connection for control input is not open: usually, you should call demoInput() in a separate R session.") return(invisible()) } on.exit(if(!identical(inputCon, stdin())) close(inputCon)) if(!is(demo, "demoSource")) demo <- .sourceAsDemo(demo) demo@envir <- as.environment(where) while(.moreSource(demo)) { .showPrompt(demo@state) control <- .getNextLine(inputCon) if(identical(control, "")) { # empty, alternate display & eval demo <- .doParseEval(demo) } else { key <- match(control, .keyStrings) if(is.na(key)) { demo@partial <- c(demo@partial, control) cat(control,"\n", sep="", file = stderr()) demo <- .doParseEval(demo, eval = TRUE, read = FALSE) } else switch(names(.keyStrings)[[key]], complete = { demo <- .doParseEval(demo, eval = TRUE) }, continue = { demo <- .nextSourceLine(demo) }, quit = { demo@state <- "quit" break }, message("Oops, got a key (\"", key, "\"), but there's no corresponding action--looks like a bug in demoSource()") ) } } ## process last expression, if any switch(demo@state, parsed = demo <- .doParseEval(demo), partial = warning("demo source ended with a partial expression: ", paste(demo@partial, collapse = "\n"))) invisible(demo) } demoExample <- function(name, package = "SoDA") { file <- exampleFiles(name, package, TRUE, TRUE) if(length(file) == 0) stop("No example file found for \"",name, "\"") demoSource(file) } setClass("demoSource", representation(lines = "character", pos = "numeric", partial = "character", expr = "expression", value = "ANY", envir = "environment", state = "character"), prototype = prototype(pos = 0, state = "initial") ) exampleFiles <- function(names = character(), where = "SoDA", oneFile = FALSE, path = TRUE) { .matchFile <- function(thisName, files) { i <- match(thisName, files) if(is.na(i)) { if(oneFile) i <- match(paste(thisName, ".R", sep=""), files) if(is.na(i)) { candidates <- paste(thisName, ".", sep = "") i <- grep(candidates, files) } } files[i] } .matchPages <- function(pages, examplePages) { if(is.null(examplePages)) stop("can't look for examples by page, no \"examplePages\" object") i <- match(pages, examplePages$Page,0) names <- as.character(examplePages$Name[i]) # may be a factor if(length(names) == 0) warning("No example names available for ", if(length(pages)==1) pages else "these pages") names } directory <- system.file("Examples", package = where) if(!nzchar(directory) ) { if(file.access(directory) < 0) stop("argument \"where\" must be the name of a package with an Examples directory or the path name of a directory") else directory <- where } files <- list.files(directory) if(length(names) == 0) files else { if(is.numeric(names)) { examplePages <- NULL data("examplePages", package = where, envir = sys.frame(sys.nframe())) names <- .matchPages(names, examplePages) } found <- character() for(thisName in names) found <- c(found, .matchFile(thisName, files)) files <- found } if(length(files) > 1 && oneFile) files <- files[menu(files, TRUE, "Please select one file")] if(path && nzchar(directory)) file.path(directory, files) else files } ## Utility functions used by the previous .moreSource <- function(demo) { (demo@pos < length(demo@lines) || (length(demo@partial) > 0)) } .sourceAsDemo <- function(source) { demo <- new("demoSource") if(is.character(source)) source = file(source) else if(!inherits(source, "connection")) stop("source must be a connection or the name of a file (got class \"",class(source), "\"") if(!isOpen(source)) { open(source, "r") on.exit(close(source), add = TRUE) } demo@lines <- readLines(source) demo@pos <- 0 demo } .evalWithVisible <- function(demo) { exprs <- demo@expr envir <- demo@envir value <- NULL for(expr in exprs) { exp2 <- substitute(withVisible(expr)) val <- tryCatch(eval(exp2, envir = envir, enclos = parent.env(envir)), error = function(e)e) if(inherits(val, "error")) { demo@value <- val demo@state <- "error" return(demo) } value <- val$value if(val$visible) print(value) } demo@value <- value demo@state <- "evaluated" demo } .tryParse <- function(text) { tt = tryCatch(parse(text = text), error = function(e)e) if(inherits(tt, "error")) { if(grep(fixed = TRUE, "unexpected end of input", tt) > 0) tt else { message(tt) expression() # do nothing } } else tt } .nextSourceLine <- function(demo) { pos <- demo@pos while(pos < length(demo@lines)) { pos <- pos+1 line <- demo@lines[[pos]] if(regexpr("#SILENT$", line)>0) { if(.nonCommentLine(line)) { demo@pos <- pos prevState <- demo@state save <- demo@partial demo@partial <- line demo <- .parseDemo(demo) demo <- .evalWithVisible(demo) demo@partial <- save demo@state <- prevState } next } cat(line, "\n", sep="", file = stderr()) demo@partial <- c(demo@partial, line) demo@pos <- pos demo@state <- "partial" # ?? probably will be set by caller break } demo } .keyStrings <- list( complete = ".", continue = ",", quit = "q" ) .doParseEval <- function(demo, eval = NA, read = TRUE) { if(is.na(eval)) { ## alternately parse and evaluate if(identical(demo@state, "parsed")) return(.evalWithVisible(demo)) else eval <- FALSE # do only the parse step this time } while(.moreSource(demo)) { if(read) demo <- .nextSourceLine(demo) demo <- .parseDemo(demo) if(identical(demo@state,"parsed")) { if(eval) demo<- .evalWithVisible(demo) break ## finished either a parse or a parse-eval step } } demo } .parseDemo <- function(demo) { expr <- .tryParse(demo@partial) if(inherits(expr, "try-error")) { demo@expr <- expression() demo@state <- "partial" .showPrompt("partial") } else { demo@state <- "parsed" demo@partial <- character() demo@expr <- expr } demo } .showPrompt <- function(state) { switch(state, partial = cat(getOption("continue"), file = stderr()), parsed = return() ,# do nothing # else, first time or evaluated or something else, like an error cat(getOption("prompt"), file = stderr()) ) } .getNextLine <- function(inputCon, pause = 1) { repeat { txt <- readLines(inputCon, 1) if(length(txt) > 0) return(txt) ## FIXME: would be better to use isIncomplete() here } } .nonCommentLine <- function(line) (regexpr("^[[:blank:]]*($|#)", line) < 0) demoInput <- function(path = "./DemoSourceFifo") { con <- .demoFifo(path, "w") repeat{ control <- readLines(n=1) cat(control, "\n", sep="", file = con) if(identical(control, "q" )) { file.remove(path) return(invisible()) } } } .demoFifo <- function(path = "./DemoSourceFifo", open = "r") { if(identical(open, "r")) { if(!file.exists(path)) NULL else fifo(path, open) } else file(path, open) # usually "w" to truncate } .previewLine <- function(demo, file) { line <- demo@lines[demo@pos+1] if(!is.na(line)) cat("## ", line, "\n", file = file) }