Skip to content

Commit

Permalink
Use "complete source" instead of top-level expression (#199)
Browse files Browse the repository at this point in the history
Fixes #194
  • Loading branch information
hadley authored Jul 3, 2024
1 parent 0d124a6 commit 212acdb
Show file tree
Hide file tree
Showing 8 changed files with 121 additions and 46 deletions.
4 changes: 2 additions & 2 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
* `parse_all()` adds a `\n` to the end of every line, even the last one if it didn't have one in the input.
* Setting `ACTIONS_STEP_DEBUG=1` (as in a failing GHA workflow) will automatically set `log_echo` and `log_warning` to `TRUE` (#175).
* New `local_reproducible_output()` helper that sets various options and env vars to help ensure consistency of output across environments.
* The `source` output handler is now passed the entire top-level expression, not just the first component.
* `evaluate()` will now terminate on the first error in a top-level expression. This matches R's own behaviour more closely.
* The `source` output handler is now passed the entire complete input expression, not just the first component.
* `evaluate()` now terminates on the first error in a multi-expression input, i.e. `1;stop('2');3` will no longer evaluate the third component. This matches console behaviour more closely.
* `is.value()` has been removed since it tests for an object that evaluate never creates.
* `parse_all()` no longer has a default method, which will generate better errors if you pass in something unexpectected.
* The package now depends on R 4.0.0 in order to decrease our maintenance burden.
Expand Down
15 changes: 15 additions & 0 deletions R/evaluate.R
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,21 @@
#' @param filename string overrriding the [base::srcfile()] filename.
#' @param include_timing Deprecated.
#' @import graphics grDevices utils
#' @examples
#' evaluate(c(
#' "1 + 1",
#' "2 + 2"
#' ))
#'
#' # Not that's there's a difference in output between putting multiple
#' # expressions on one line vs spreading them across multiple lines
#' evaluate("1;2;3")
#' evaluate(c("1", "2", "3"))
#'
#' # This also affects how errors propagate, matching the behaviour
#' # of the R console
#' evaluate("1;stop(2);3")
#' evaluate(c("1", "stop(2)", "3"))
evaluate <- function(input,
envir = parent.frame(),
enclos = NULL,
Expand Down
13 changes: 7 additions & 6 deletions R/output-handler.R
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@
#' printing, then the `text` or `graphics` handlers may be called.
#'
#' @param source Function to handle the echoed source code under evaluation.
#' This function should take two arguments (`src` and `tle`), and return
#' an object that will be inserted into the evaluate outputs. `src` is the
#' unparsed text of the source code, and `tle` is the parsed top-level
#' expression. If `src` is unparsable, `tle` will be `expression()`.
#' This function should take two arguments (`src` and `expr`), and return
#' an object that will be inserted into the evaluate outputs. `src` is the
#' unparsed text of the source code, and `expr` is the complete input
#' expression (which may have 0, 1, 2, or more components; see [parse_all()]
#' for details).
#'
#' Return `src` for the default evaluate behaviour. Return `NULL` to
#' drop the source from the output.
#' Return `src` for the default evaluate behaviour. Return `NULL` to
#' drop the source from the output.
#' @param text Function to handle any textual console output.
#' @param graphics Function to handle graphics, as returned by
#' [recordPlot()].
Expand Down
55 changes: 38 additions & 17 deletions R/parse_all.R
Original file line number Diff line number Diff line change
Expand Up @@ -8,38 +8,59 @@
#' @param filename string overriding the file name
#' @param allow_error whether to allow syntax errors in `x`
#' @return
#' A data frame two columns, `src` and `expr`, and one row for each top-level
#' expression in `x`.
#' A data frame two columns, `src` and `expr`, and one row for each complete
#' input in `x`. A complete input is R code that would trigger execution when
#' typed at the console. This might consist of multiple expressions separated
#' by `;` or one expression spread over multiple lines (like a function
#' definition).
#'
#' `src` is a character vector of source code. Each element represents a
#' complete line (or multi-line) expression, i.e. it always has a terminal `\n`.
#' complete input expression (which might span multiple line) and always has a
#' terminal `\n`.
#'
#' `expr`, a list-column of top-level expressions. A top-level expression
#' is a complete expression which would trigger execution if typed at the
#' console. Each element is an [expression()] object, which can be of any
#' length. It will be length:
#' `expr` is a list-column of [expression]s. The expressions can be of any
#' length, depending on the structure of the complete input source:
#'
#' * 0 if the top-level expression contains only whitespace and/or comments.
#' * 1 if the top-level expression is a single scalar (
#' like `TRUE`, `1`, or `"x"`), name, or call
#' * 2 or more if the top-level expression uses `;` to put multiple expressions
#' on one line.
#' * If `src` consists of only only whitespace and/or comments, `expr` will
#' be length 0.
#' * If `src` a single scalar (like `TRUE`, `1`, or `"x"`), name, or
#' function call, `expr` will be length 1.
#' * If `src` contains multiple expressions separated by `;`, `expr` will
#' have length two or more.
#'
#' The expressions have their srcrefs removed.
#'
#' If there are syntax errors in `x` and `allow_error = TRUE`, the data
#' frame will have an attribute `PARSE_ERROR` that stores the error object.
#' @export
#' @examples
#' source <- "
#' # a comment
#' x
#' x;y
#' "
#' # Each of these inputs are single line, but generate different numbers of
#' # expressions
#' source <- c(
#' "# a comment",
#' "x",
#' "x;y",
#' "x;y;z"
#' )
#' parsed <- parse_all(source)
#' lengths(parsed$expr)
#' str(parsed$expr)
#'
#' # Each of these inputs are a single expression, but span different numbers
#' # of lines
#' source <- c(
#' "function() {}",
#' "function() {",
#' " # Hello!",
#' "}",
#' "function() {",
#' " # Hello!",
#' " # Goodbye!",
#' "}"
#' )
#' parsed <- parse_all(source)
#' lengths(parsed$expr)
#' parsed$src
parse_all <- function(x, filename = NULL, allow_error = FALSE) UseMethod("parse_all")

#' @export
Expand Down
16 changes: 16 additions & 0 deletions man/evaluate.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 4 additions & 3 deletions man/new_output_handler.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

55 changes: 38 additions & 17 deletions man/parse_all.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion tests/testthat/test-conditions.R
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ test_that("log_warning causes warnings to be emitted", {

# errors ----------------------------------------------------------------------

test_that("an error terminates evaluation of top-level expression", {
test_that("an error terminates evaluation of multi-expression input", {
ev <- evaluate("stop('1');2\n3")
expect_output_types(ev, c("source", "error", "source", "text"))
expect_equal(ev[[1]]$src, "stop('1');2\n")
Expand Down

0 comments on commit 212acdb

Please sign in to comment.