Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use "complete source" instead of top-level expression #199

Merged
merged 5 commits into from
Jul 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading